locks/halibut

View on GitHub
lib/halibut/core/link.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Halibut::Core
  # This class represents a HAL Link object.
  #
  # spec spec spec.
  class Link
    extend Forwardable

    # The URI associated with this link.
    attr_reader :href

    def_delegators :@options, :templated, :templated?, :type,
                   :name, :profile, :title, :hreflang

    # Returns an instance of a HAL Link object
    #
    # @example Link with no options
    #     link = Link.new('http://homeopathy.org')
    #
    # @example Link with name and type options
    #     link = Link.new('http://homeopathy.org', name: 'Homeopath'
    #                                            , type: 'text/html')
    #
    # If you pass an options that is not one of the reserved link properties
    # as defined by the spec, they are dropped.
    # It should possibly raise an error, hafta think about it.
    #
    # @param [String]  href      URI or URI Template
    # @param [Hash]    opts      Options: type, name, profile, title, hreflang
    #
    # @return [Halibut::Core::Link] HAL Link object
    def initialize(href, opts={})
      @href    = href
      @options = Options.new opts
    end

    # Simply returns a hash of the href and the options that are not empty.
    #
    # @example
    #     link = Link.new('/links', name: 'Links')
    #     link.to_hash
    #     # => { "href" => "/links", "name" => "links" }
    #
    # @return [Hash] hash from Link Object
    def to_hash
      { 'href' => href }.merge @options
    end

    # Generic comparison method.
    #
    # Two objects are the same if they have the same href and the same options.
    #
    # @example
    #     link_one = Link.new('/link', name: 'One', type: 'text/html')
    #     link_two = Link.new('/link', name: 'One', type: 'text/html')
    #     link_one == link_two
    #     # => true
    #
    # @param [Link] other Link object to compare to
    # @return [true,false] return of the comparison of the two objects
    def ==(other)
      @href == other.href && @options == other.options
    end

    protected
    attr_reader :options

    private
    # Options reifies the various optional properties of a link, as per the
    # spec: templated, type, name, profile, title, hreflang.
    #
    # @example
    #     hash = { name: 'John le Carré', templated: true }
    #     opts = Options.new(hash)
    #     opts.name    # => John le Carré
    #     opts['name'] # => John le Carré
    #     opts[:name]  # => John le Carré
    #
    class Options
      attr_reader :templated, :type, :name,
                  :profile, :title, :hreflang

      # Initializes a link options object.
      # Available options:
      #   * templated
      #   * type
      #   * name
      #   * profile
      #   * title
      #   * hreflang
      #
      # @example Builds an options object with a name and a title
      #     Options.new({
      #       :name => "Apple",
      #       :title => "Swift"
      #     })
      #     #=> #<Halibut::Core::Link::Options:0x007f80c880c460
      #       @hreflang=nil,
      #       @name="Apple",
      #       @profile=nil,
      #       @templated=nil,
      #       @title="Swift",
      #       @type=nil>
      #
      # @param [Hash] opts options settings
      def initialize opts
        string_options = Helpers::stringify_keys(opts)

        @templated = opts[:templated] || opts['templated']
        @type      = opts[:type]      || opts['type']
        @name      = opts[:name]      || opts['name']
        @profile   = opts[:profile]   || opts['profile']
        @title     = opts[:title]     || opts['title']
        @hreflang  = opts[:hreflang]  || opts['hreflang']
      end

      # Tells us if the href of the associated link is templated.
      #
      # The reason for not returning @templated directly is that all of the
      # options are optional, thus nil could be returned instead of a boolean.
      #
      # @return [true, false] whether the href is a templated uri or not.
      def templated?
        @templated || false
      end

      # When converting to a hash, options that weren't set (.nil? == true) are
      # kept out.
      #
      # This might have some implications, as it does not 'serialiaze' options
      # that were explicitely set to nil. On the other hand, one can argue that
      # if they were explicitly set to nil, then they shouldn't show up anyway.
      def to_hash
        instance_variables.each_with_object({}) do |ivar, hash|
          name  = ivar.to_s.reverse.chomp("@").reverse
          value = instance_variable_get(ivar)

          next if value.nil?

          hash[name] = value
        end
      end

      # Straight forward comparison between two Options objects.
      #
      # @example
      #     opts_one = Options.new(name: 'Link', templated: true)
      #     opts_two = Options.new(name: 'Link', templated: true)
      #     opts_one == opts_two
      #     # => true
      #
      # @param [Options] other Options object to compare to.
      # @return [true,false] whether these two objects are equivalent or not.
      def ==(other)
        to_hash == other.to_hash
      end

      private
      module Helpers
        def self.stringify_keys(hash)
          hash.each_with_object({}) {|(k,v), obj| obj[k.to_s] = v }
        end
      end
    end
  end
end