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
|