File: hash.rb

package info (click to toggle)
ruby-virtus 2.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 660 kB
  • sloc: ruby: 4,378; makefile: 2
file content (130 lines) | stat: -rw-r--r-- 3,400 bytes parent folder | download | duplicates (3)
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
module Virtus
  class Attribute

    # Handles attributes with Hash type
    #
    class Hash < Attribute
      primitive ::Hash
      default   primitive.new

      # @api private
      attr_reader :key_type, :value_type

      # FIXME: remove this once axiom-types supports it
      #
      # @private
      Type = Struct.new(:key_type, :value_type) do
        def self.infer(type)
          if axiom_type?(type)
            new(type.key_type, type.value_type)
          else
            type_options = infer_key_and_value_types(type)
            key_class    = determine_type(type_options.fetch(:key_type,   Object))
            value_class  = determine_type(type_options.fetch(:value_type, Object))

            new(key_class, value_class)
          end
        end

        # @api private
        def self.pending?(primitive)
          primitive.is_a?(String) || primitive.is_a?(Symbol)
        end

        # @api private
        def self.axiom_type?(type)
          type.is_a?(Class) && type < Axiom::Types::Type
        end

        # @api private
        def self.determine_type(type)
          return type if pending?(type)

          if EmbeddedValue.handles?(type)
            type
          else
            Axiom::Types.infer(type)
          end
        end

        # @api private
        def self.infer_key_and_value_types(type)
          return {} unless type.kind_of?(::Hash)

          if type.size > 1
            raise ArgumentError, "more than one [key => value] pair in `#{type}`"
          else
            key_type, value_type = type.keys.first, type.values.first

            key_primitive =
              if key_type.is_a?(Class) && key_type < Attribute && key_type.primitive
                key_type.primitive
              else
                key_type
              end

            value_primitive =
              if value_type.is_a?(Class) && value_type < Attribute && value_type.primitive
                value_type.primitive
              else
                value_type
              end

            { :key_type => key_primitive, :value_type => value_primitive}
          end
        end

        # @api private
        def coercion_method
          :to_hash
        end

        # @api private
        def primitive
          ::Hash
        end
      end

      # @api private
      def self.build_type(definition)
        Type.infer(definition.type)
      end

      # @api private
      def self.merge_options!(type, options)
        options[:key_type]   ||= Attribute.build(type.key_type, :strict => options[:strict])
        options[:value_type] ||= Attribute.build(type.value_type, :strict => options[:strict])
      end

      # Coerce members
      #
      # @see [Attribute#coerce]
      #
      # @api public
      def coerce(*)
        coerced = super

        return coerced unless coerced.respond_to?(:each_with_object)

        coerced.each_with_object({}) do |(key, value), hash|
          hash[key_type.coerce(key)] = value_type.coerce(value)
        end
      end

      # @api private
      def finalize
        return self if finalized?
        @key_type   = options[:key_type].finalize
        @value_type = options[:value_type].finalize
        super
      end

      # @api private
      def finalized?
        super && key_type.finalized? && value_type.finalized?
      end

    end # class Hash

  end # class Attribute
end # module Virtus