yaks/lib/yaks/format/hal.rb

Summary

Maintainability
A
0 mins
Test Coverage
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