lib/halibut/core/resource.rb
require 'halibut/core/relation_map'
require 'halibut/core/link'
module Halibut::Core
# This class represents a HAL Resource object.
#
# spec spec spec
class Resource
# All the properties set in this resource
attr_reader :properties
# A collection of links, grouped by relation
attr_reader :links
# A collection of embedded resources, grouped by relation.
attr_reader :embedded
# A collection of namespaces defined in the document
attr_reader :namespace
# Initialize a new Resource.
#
# As defined in the spec, the resource SHOULD have a self link, but it
# isn't required.
# Also optionally, I'm toying with the idea of being able to pass in
# properties, links and embedded resources as parameters to this method,
# like suggested in https://github.com/locks/halibut/issues/1.
#
# @example Resource without self link (e.g. POSTing a new resource)
# resource = Halibut::Core::Resource.new
# resource.set_property :name, 'Halibut Rules'
# resource.set_property :winner, 'Tiger Blood'
#
# @example Resource with a self link
# resource = Halibut::Core::Resource.new
#
# @param [String] href link that will be added to the self relation.
# @param [Hash] properties resources properties.
# @param [Hash] links resource links.
# @param [Hash] embedded embedded resources.
def initialize(href=nil, properties={}, links={}, embedded={})
@namespaces = RelationMap.new
@links = RelationMap.new
@embedded = RelationMap.new
@embedded_arrays = RelationMap.new(single_item_arrays: true)
@properties = {}
add_link('self', href) if href
end
# Returns the self link of the resource.
#
# @return [String,nil] the self link of the resource
def href
@links.fetch('self', []).map(&:href).first
end
# Returns the namespace associated with the name.
#
# @param [String] name the name for which you want to retrieve a namespace
# for.
# @return [Halibut::Core::Link]
def namespace(name)
@links['curie'].select {|ns| ns.name == name }.first
end
alias_method :ns, :namespace
def namespaces
@links['curie']
end
# Sets a property in the resource.
#
# @example
# resource = Halibut::Core::Resource.new
# resource.set_property :name, 'FooBar'
# resource.property :name
# # => "FooBar"
#
# @param [Object] property the key
# @param [Object] value the value
def set_property(property, value)
if property == '_links' || property == '_embedded'
raise ArgumentError, "Argument #{property} is a reserved property"
end
tap { @properties[property] = value }
end
def []=(property, value)
tap { @properties[property] = value }
end
# Returns the value of a property in the resource
#
# @example
# resource = Halibut::Core::Resource.new
# resource.set_property :name, 'FooBar'
# resource.property :name
# # => "FooBar"
#
# @param [String] property property
def property(property)
@properties.fetch(property, nil)
end
# Adds a namespace to the resource.
#
# @param [String] name The name of the namespace
# @param [String] href The templated URI of the namespace
def add_namespace(name, href)
add_link 'curie', href, templated: true, name: name
end
# Adds link to relation.
#
# @example
# resource = Halibut::Core::Resource.new
# resource.add_link 'next', '/resource/2', name: 'Foo'
# link = resource.links['next'].first
# link.href
# # => "/resource/2"
# link.name
# # => "Foo"
#
# @param [String] relation relation
# @param [String] href href
# @param [Hash] opts options: templated, type, name, profile,
# title, hreflang
def add_link(relation, href, opts={})
@links.add relation, Link.new(href, opts)
end
# Embeds resource in relation
#
# To embed many resources, call #add_embedded_resource with each item
#
# @param [String] relation relation
# @param [Resource] resource resource to embed
def embed_resource(relation, resource)
warn 'Calling Halibut::Core::Resource#embed_resource for populating an array is deprecated. Use Halibut::Core::Resource#add_embedded_resource instead.' if @embedded[relation]
@embedded.add relation, resource
end
# Embeds resource in a relation array
#
# To embed a single object, see #embed_resource.
#
# @param [String] relation relation
# @param [Resource] resource resource to embed
def add_embedded_resource(relation, resource)
@embedded_arrays.add relation, resource
end
# Hash representation of the resource.
# Will ommit links and embedded keys if they're empty
#
# @return [Hash] hash representation of the resource
def to_hash
{}.merge(@properties).tap do |h|
h['_links'] = {}.merge @links unless @links.empty?
combined_embedded = {}
combined_embedded.merge! @embedded unless @embedded.empty?
combined_embedded.merge! @embedded_arrays unless @embedded_arrays.empty?
h['_embedded'] = combined_embedded unless combined_embedded.empty?
end
end
# Compares two resources.
#
# @param [Halibut::Core::Resource] other Resource to compare to
# @return [true, false] Result of the comparison
def ==(other)
@properties == other.properties &&
@links == other.links &&
@embedded == other.embedded
end
end
end