yaks/lib/yaks/format/hal.rb
module Yaks
class Format
# Hypertext Application Language (http://stateless.co/hal_specification.html)
#
# A lightweight JSON Hypermedia message format.
#
# Options: +:plural_links+ In HAL, a single rel can correspond to
# a single link, or to a list of links. Which rels are singular
# and which are plural is application-dependant. Yaks assumes all
# links are singular. If your resource might contain multiple
# links for the same rel, then configure that rel to be plural. In
# that case it will always be rendered as a collection, even when
# the resource only contains a single link.
#
# @example
#
# yaks = Yaks.new do
# format_options :hal, {plural_links: [:related_content]}
# end
#
class Hal < self
register :hal, :json, 'application/hal+json'
def transitive?
true
end
def inverse
Yaks::Reader::Hal.new
end
protected
# @param [Yaks::Resource] resource
# @return [Hash]
def serialize_resource(resource)
# The HAL spec doesn't say explicitly how to deal missing values,
# looking at client behavior (Hyperagent) it seems safer to return an empty
# resource.
#
result = resource.attributes
if resource.links.any?
result = result.merge(_links: serialize_links(resource.links))
end
if resource.collection?
result = result.merge(_embedded:
serialize_embedded([resource]))
elsif resource.subresources.any?
result = result.merge(_embedded:
serialize_embedded(resource.subresources))
end
result
end
# @param [Array] links
# @return [Hash]
def serialize_links(links)
links.reduce({}, &method(:serialize_link))
end
# @param [Hash] memo
# @param [Yaks::Resource::Link]
# @return [Hash]
def serialize_link(memo, link)
hal_link = {href: link.uri}
hal_link.merge!(link.options)
memo[link.rel] = if singular?(link.rel)
hal_link
else
(memo[link.rel] || []) + [hal_link]
end
memo
end
# @param [String] rel
# @return [Boolean]
def singular?(rel)
!options.fetch(:plural_links) { [] }.include?(rel)
end
# @param [Array] subresources
# @return [Hash]
def serialize_embedded(subresources)
subresources.each_with_object({}) do |sub, memo|
memo[sub.rels.first] = if sub.collection?
sub.map(&method(:serialize_resource))
elsif sub.null_resource?
nil
else
serialize_resource(sub)
end
end
end
end
end
end