Skip to content
This repository
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 132 lines (119 sloc) 3.967 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
module Virtus
  class Attribute

    # Hash
    #
    # @example
    # class Post
    # include Virtus
    #
    # attribute :meta, Hash
    # end
    #
    # Post.new(:meta => { :tags => %w(foo bar) })
    #
    class Hash < Object
      primitive ::Hash
      coercion_method :to_hash

      # The type to which keys of this hash will be coerced
      #
      # @example
      #
      # class Request
      # include Virtus
      #
      # attribute :headers, Hash[Symbol => String]
      # end
      #
      # Post.attributes[:headers].key_type # => Virtus::Attribute::Symbol
      #
      # @return [Virtus::Attribute]
      #
      # @api public
      attr_reader :key_type

      # The type to which values of this hash will be coerced
      #
      # @example
      #
      # class Request
      # include Virtus
      #
      # attribute :headers, Hash[Symbol => String]
      # end
      #
      # Post.attributes[:headers].value_type # => Virtus::Attribute::String
      #
      # @return [Virtus::Attribute]
      #
      # @api public
      attr_reader :value_type


      # Handles hashes with [key_type => value_type] syntax.
      #
      # @param [Class]
      #
      # @param [Hash]
      #
      # @return [Hash]
      #
      # @api private
      def self.merge_options(type, options)
        if !type.respond_to?(:size)
          options
        elsif type.size > 1
          raise ArgumentError, "more than one [key => value] pair in `#{type.inspect}`"
        else
          options.merge(:key_type => type.keys.first, :value_type => type.values.first)
        end
      end

      # Initializes an instance of {Virtus::Attribute::Hash}
      #
      # @api private
      def initialize(*)
        super
        if @options.has_key?(:key_type) && @options.has_key?(:value_type)
          @key_type = @options[:key_type]
          @value_type = @options[:value_type]
          @key_type_instance = Attribute.build(@name, @key_type)
          @value_type_instance = Attribute.build(@name, @value_type)
        end
      end

      # Coerce a hash with keys and values
      #
      # @param [Object]
      #
      # @return [Object]
      #
      # @api private
      def coerce(value)
        coerced = super
        return coerced unless coerced.respond_to?(:inject)
        coerced.inject(new_hash) do |hash, key_and_value|
          hash[key_and_value[0]] = key_and_value[1]
          hash
        end
      end

      # Return an instance of the Hash with redefined []= method to coerce
      # keys and values on assigning.
      #
      # @return [Hash]
      #
      # @api private
      def new_hash
        hash = self.class.primitive.new
        return hash unless @key_type_instance && @value_type_instance

        key_coercion_method = @key_type_instance.coercion_method
        value_coercion_method = @value_type_instance.coercion_method

        # Redefine []= method to coerce key and value on assigning.
        # It requires inlining of Attribute#coerce method to coerce.
        # An alternative way would be using define_singleton_method or Sinatra's meta_def.
        hash.instance_eval(<<-eorb, __FILE__, __LINE__+1)
def []=(key, value) # def []=(key, value)
coerced_key = Virtus::Coercion[key.class].#{key_coercion_method}(key) # coerced_key = Virtus::Coercion[key.class].to_sym(key)
coerced_value = Virtus::Coercion[value.class].#{value_coercion_method}(value) # coerced_value = Virtus::Coercion[value.class].to_f(value)
super(coerced_key, coerced_value) # super(coerced_key, coerced_value)
end # end
eorb

        hash
      end

    end # class Hash
  end # class Attribute
end # module Virtus
Something went wrong with that request. Please try again.