virtus2 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +39 -0
- data/.rspec +2 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +18 -0
- data/Changelog.md +258 -0
- data/Gemfile +10 -0
- data/Guardfile +19 -0
- data/LICENSE +20 -0
- data/README.md +630 -0
- data/Rakefile +15 -0
- data/TODO.md +6 -0
- data/lib/virtus/attribute/accessor.rb +103 -0
- data/lib/virtus/attribute/boolean.rb +55 -0
- data/lib/virtus/attribute/builder.rb +182 -0
- data/lib/virtus/attribute/coercer.rb +45 -0
- data/lib/virtus/attribute/coercible.rb +20 -0
- data/lib/virtus/attribute/collection.rb +103 -0
- data/lib/virtus/attribute/default_value/from_callable.rb +35 -0
- data/lib/virtus/attribute/default_value/from_clonable.rb +35 -0
- data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
- data/lib/virtus/attribute/default_value.rb +51 -0
- data/lib/virtus/attribute/embedded_value.rb +67 -0
- data/lib/virtus/attribute/enum.rb +45 -0
- data/lib/virtus/attribute/hash.rb +130 -0
- data/lib/virtus/attribute/lazy_default.rb +18 -0
- data/lib/virtus/attribute/nullify_blank.rb +24 -0
- data/lib/virtus/attribute/strict.rb +26 -0
- data/lib/virtus/attribute.rb +245 -0
- data/lib/virtus/attribute_set.rb +240 -0
- data/lib/virtus/builder/hook_context.rb +51 -0
- data/lib/virtus/builder.rb +133 -0
- data/lib/virtus/class_inclusions.rb +48 -0
- data/lib/virtus/class_methods.rb +90 -0
- data/lib/virtus/coercer.rb +41 -0
- data/lib/virtus/configuration.rb +72 -0
- data/lib/virtus/const_missing_extensions.rb +18 -0
- data/lib/virtus/extensions.rb +105 -0
- data/lib/virtus/instance_methods.rb +218 -0
- data/lib/virtus/model.rb +68 -0
- data/lib/virtus/module_extensions.rb +88 -0
- data/lib/virtus/support/equalizer.rb +128 -0
- data/lib/virtus/support/options.rb +113 -0
- data/lib/virtus/support/type_lookup.rb +109 -0
- data/lib/virtus/value_object.rb +150 -0
- data/lib/virtus/version.rb +3 -0
- data/lib/virtus.rb +310 -0
- data/spec/integration/attributes_attribute_spec.rb +28 -0
- data/spec/integration/building_module_spec.rb +90 -0
- data/spec/integration/collection_member_coercion_spec.rb +96 -0
- data/spec/integration/custom_attributes_spec.rb +42 -0
- data/spec/integration/custom_collection_attributes_spec.rb +101 -0
- data/spec/integration/default_values_spec.rb +87 -0
- data/spec/integration/defining_attributes_spec.rb +86 -0
- data/spec/integration/embedded_value_spec.rb +50 -0
- data/spec/integration/extending_objects_spec.rb +35 -0
- data/spec/integration/hash_attributes_coercion_spec.rb +54 -0
- data/spec/integration/inheritance_spec.rb +42 -0
- data/spec/integration/injectible_coercers_spec.rb +48 -0
- data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
- data/spec/integration/overriding_virtus_spec.rb +46 -0
- data/spec/integration/required_attributes_spec.rb +25 -0
- data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
- data/spec/integration/using_modules_spec.rb +55 -0
- data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
- data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
- data/spec/integration/virtus/value_object_spec.rb +99 -0
- data/spec/shared/constants_helpers.rb +9 -0
- data/spec/shared/freeze_method_behavior.rb +40 -0
- data/spec/shared/idempotent_method_behaviour.rb +5 -0
- data/spec/shared/options_class_method.rb +19 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +43 -0
- data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +25 -0
- data/spec/unit/virtus/attribute/class_methods/build_spec.rb +180 -0
- data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +32 -0
- data/spec/unit/virtus/attribute/coerce_spec.rb +129 -0
- data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +20 -0
- data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +105 -0
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +74 -0
- data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
- data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
- data/spec/unit/virtus/attribute/custom_collection_spec.rb +29 -0
- data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
- data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +70 -0
- data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +91 -0
- data/spec/unit/virtus/attribute/get_spec.rb +32 -0
- data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +106 -0
- data/spec/unit/virtus/attribute/hash/coerce_spec.rb +92 -0
- data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +20 -0
- data/spec/unit/virtus/attribute/rename_spec.rb +16 -0
- data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
- data/spec/unit/virtus/attribute/set_default_value_spec.rb +107 -0
- data/spec/unit/virtus/attribute/set_spec.rb +29 -0
- data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +19 -0
- data/spec/unit/virtus/attribute_set/append_spec.rb +47 -0
- data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +36 -0
- data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +36 -0
- data/spec/unit/virtus/attribute_set/each_spec.rb +65 -0
- data/spec/unit/virtus/attribute_set/element_reference_spec.rb +17 -0
- data/spec/unit/virtus/attribute_set/element_set_spec.rb +64 -0
- data/spec/unit/virtus/attribute_set/merge_spec.rb +34 -0
- data/spec/unit/virtus/attribute_set/reset_spec.rb +71 -0
- data/spec/unit/virtus/attribute_spec.rb +229 -0
- data/spec/unit/virtus/attributes_reader_spec.rb +41 -0
- data/spec/unit/virtus/attributes_writer_spec.rb +51 -0
- data/spec/unit/virtus/class_methods/finalize_spec.rb +67 -0
- data/spec/unit/virtus/class_methods/new_spec.rb +39 -0
- data/spec/unit/virtus/config_spec.rb +13 -0
- data/spec/unit/virtus/element_reader_spec.rb +21 -0
- data/spec/unit/virtus/element_writer_spec.rb +19 -0
- data/spec/unit/virtus/freeze_spec.rb +41 -0
- data/spec/unit/virtus/model_spec.rb +197 -0
- data/spec/unit/virtus/module_spec.rb +174 -0
- data/spec/unit/virtus/set_default_attributes_spec.rb +32 -0
- data/spec/unit/virtus/value_object_spec.rb +138 -0
- data/virtus2.gemspec +26 -0
- metadata +225 -0
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "rspec/core/rake_task"
|
2
|
+
|
3
|
+
RSpec::Core::RakeTask.new(:spec)
|
4
|
+
task default: [:spec]
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "rubocop/rake_task"
|
8
|
+
|
9
|
+
Rake::Task[:default].enhance [:rubocop]
|
10
|
+
|
11
|
+
RuboCop::RakeTask.new do |task|
|
12
|
+
task.options << "--display-cop-names"
|
13
|
+
end
|
14
|
+
rescue LoadError
|
15
|
+
end
|
data/TODO.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Accessor extension provides methods to read and write attributes
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# attribute = Virtus::Attribute.build(String, :name => :email)
|
9
|
+
# model = Class.new { attr_reader :email }
|
10
|
+
# object = model.new
|
11
|
+
#
|
12
|
+
# attribute.set(object, 'jane@doe.com')
|
13
|
+
# attribute.get(object) # => 'jane@doe.com'
|
14
|
+
#
|
15
|
+
module Accessor
|
16
|
+
|
17
|
+
# Return name of this accessor attribute
|
18
|
+
#
|
19
|
+
# @return [Symbol]
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
attr_reader :name
|
23
|
+
|
24
|
+
# Return instance_variable_name used by this accessor
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
attr_reader :instance_variable_name
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def self.extended(descendant)
|
31
|
+
super
|
32
|
+
name = descendant.options.fetch(:name).to_sym
|
33
|
+
descendant.instance_variable_set('@name', name)
|
34
|
+
descendant.instance_variable_set('@instance_variable_name', "@#{name}")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return if attribute value is defined
|
38
|
+
#
|
39
|
+
# @param [Object] instance
|
40
|
+
#
|
41
|
+
# @return [Boolean]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def defined?(instance)
|
45
|
+
instance.instance_variable_defined?(instance_variable_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return value of the attribute
|
49
|
+
#
|
50
|
+
# @param [Object] instance
|
51
|
+
#
|
52
|
+
# @return [Object]
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def get(instance)
|
56
|
+
instance.instance_variable_get(instance_variable_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set value of the attribute
|
60
|
+
#
|
61
|
+
# @param [Object] instance
|
62
|
+
# @param [Object] value
|
63
|
+
#
|
64
|
+
# @return [Object] value that was set
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def set(instance, value)
|
68
|
+
instance.instance_variable_set(instance_variable_name, value)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set default value
|
72
|
+
#
|
73
|
+
# @param [Object] instance
|
74
|
+
#
|
75
|
+
# @return [Object] value that was set
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def set_default_value(instance)
|
79
|
+
set(instance, default_value.call(instance, self))
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns a Boolean indicating whether the reader method is public
|
83
|
+
#
|
84
|
+
# @return [Boolean]
|
85
|
+
#
|
86
|
+
# @api private
|
87
|
+
def public_reader?
|
88
|
+
options[:reader] == :public
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns a Boolean indicating whether the writer method is public
|
92
|
+
#
|
93
|
+
# @return [Boolean]
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
def public_writer?
|
97
|
+
options[:writer] == :public
|
98
|
+
end
|
99
|
+
|
100
|
+
end # Accessor
|
101
|
+
|
102
|
+
end # Attribute
|
103
|
+
end # Virtus
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Boolean attribute allows true or false values to be set
|
5
|
+
# Additionally it adds boolean reader method, like "admin?"
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class Post
|
9
|
+
# include Virtus
|
10
|
+
#
|
11
|
+
# attribute :published, Boolean
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# post = Post.new(:published => false)
|
15
|
+
# post.published? # => false
|
16
|
+
#
|
17
|
+
class Boolean < Attribute
|
18
|
+
primitive TrueClass
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def self.build_type(*)
|
22
|
+
Axiom::Types::Boolean
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns if the given value is either true or false
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# boolean = Virtus::Attribute::Boolean.new(:bool)
|
29
|
+
# boolean.value_coerced?(true) # => true
|
30
|
+
# boolean.value_coerced?(false) # => true
|
31
|
+
# boolean.value_coerced?(1) # => false
|
32
|
+
# boolean.value_coerced?('true') # => false
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def value_coerced?(value)
|
38
|
+
value.equal?(true) || value.equal?(false)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Creates an attribute reader method as a query
|
42
|
+
#
|
43
|
+
# @param [Module] mod
|
44
|
+
#
|
45
|
+
# @return [undefined]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
def define_accessor_methods(attribute_set)
|
49
|
+
super
|
50
|
+
attribute_set.define_reader_method(self, "#{name}?", options[:reader])
|
51
|
+
end
|
52
|
+
|
53
|
+
end # class Boolean
|
54
|
+
end # class Attribute
|
55
|
+
end # module Virtus
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Virtus
|
2
|
+
|
3
|
+
# Attribute placeholder used when type constant is passed as a string or symbol
|
4
|
+
#
|
5
|
+
# @private
|
6
|
+
class PendingAttribute
|
7
|
+
attr_reader :type, :options, :name
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
def initialize(type, options)
|
11
|
+
@type, @options = type.to_s, options
|
12
|
+
@name = options[:name]
|
13
|
+
end
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
def finalize
|
17
|
+
Attribute::Builder.call(determine_type, options).finalize
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def finalized?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def determine_type
|
27
|
+
if type.include?('::')
|
28
|
+
Virtus.constantize(type)
|
29
|
+
else
|
30
|
+
Object.const_get(type)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end # PendingAttribute
|
35
|
+
|
36
|
+
# Extracts the actual type primitive from input type
|
37
|
+
#
|
38
|
+
# @private
|
39
|
+
class TypeDefinition
|
40
|
+
attr_reader :type, :primitive
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def initialize(type)
|
44
|
+
@type = type
|
45
|
+
initialize_primitive
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
def pending?
|
50
|
+
@pending if defined?(@pending)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def initialize_primitive
|
57
|
+
@primitive =
|
58
|
+
if type.instance_of?(String) || type.instance_of?(Symbol)
|
59
|
+
if !type.to_s.include?('::') && Object.const_defined?(type)
|
60
|
+
Object.const_get(type)
|
61
|
+
elsif not Attribute::Builder.determine_type(type)
|
62
|
+
@pending = true
|
63
|
+
type
|
64
|
+
else
|
65
|
+
type
|
66
|
+
end
|
67
|
+
elsif not type.is_a?(Class)
|
68
|
+
type.class
|
69
|
+
else
|
70
|
+
type
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class Attribute
|
76
|
+
|
77
|
+
# Builder is used to set up an attribute instance based on input type and options
|
78
|
+
#
|
79
|
+
# @private
|
80
|
+
class Builder
|
81
|
+
attr_reader :attribute, :options, :type_definition, :klass, :type
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
def self.call(type, options = {})
|
85
|
+
type_definition = TypeDefinition.new(type)
|
86
|
+
|
87
|
+
if type_definition.pending?
|
88
|
+
PendingAttribute.new(type, options)
|
89
|
+
else
|
90
|
+
new(type_definition, options).attribute
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api private
|
95
|
+
def self.determine_type(klass, default = nil)
|
96
|
+
type = Attribute.determine_type(klass)
|
97
|
+
|
98
|
+
if klass.is_a?(Class)
|
99
|
+
type ||=
|
100
|
+
if klass < Axiom::Types::Type
|
101
|
+
determine_type(klass.primitive)
|
102
|
+
elsif EmbeddedValue.handles?(klass)
|
103
|
+
EmbeddedValue
|
104
|
+
elsif klass < Enumerable && !(klass <= Range)
|
105
|
+
Collection
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
type || default
|
110
|
+
end
|
111
|
+
|
112
|
+
# @api private
|
113
|
+
def initialize(type_definition, options)
|
114
|
+
@type_definition = type_definition
|
115
|
+
|
116
|
+
initialize_class
|
117
|
+
initialize_type
|
118
|
+
initialize_options(options)
|
119
|
+
initialize_default_value
|
120
|
+
initialize_coercer
|
121
|
+
initialize_attribute
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
# @api private
|
127
|
+
def initialize_class
|
128
|
+
@klass = self.class.determine_type(type_definition.primitive, Attribute)
|
129
|
+
end
|
130
|
+
|
131
|
+
# @api private
|
132
|
+
def initialize_type
|
133
|
+
@type = klass.build_type(type_definition)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @api private
|
137
|
+
def initialize_options(options)
|
138
|
+
@options = klass.options.merge(:coerce => Virtus.coerce).update(options)
|
139
|
+
klass.merge_options!(type, @options)
|
140
|
+
determine_visibility
|
141
|
+
end
|
142
|
+
|
143
|
+
# @api private
|
144
|
+
def initialize_default_value
|
145
|
+
options.update(:default_value => DefaultValue.build(options[:default]))
|
146
|
+
end
|
147
|
+
|
148
|
+
# @api private
|
149
|
+
def initialize_coercer
|
150
|
+
options.update(:coercer => determine_coercer)
|
151
|
+
end
|
152
|
+
|
153
|
+
# @api private
|
154
|
+
def initialize_attribute
|
155
|
+
@attribute = klass.new(type, options)
|
156
|
+
|
157
|
+
@attribute.extend(Accessor) if options[:name]
|
158
|
+
@attribute.extend(Coercible) if options[:coerce]
|
159
|
+
@attribute.extend(NullifyBlank) if options[:nullify_blank]
|
160
|
+
@attribute.extend(Strict) if options[:strict]
|
161
|
+
@attribute.extend(LazyDefault) if options[:lazy]
|
162
|
+
|
163
|
+
@attribute.finalize if options[:finalize]
|
164
|
+
end
|
165
|
+
|
166
|
+
# @api private
|
167
|
+
def determine_coercer
|
168
|
+
options.fetch(:coercer) { klass.build_coercer(type, options) }
|
169
|
+
end
|
170
|
+
|
171
|
+
# @api private
|
172
|
+
def determine_visibility
|
173
|
+
default_accessor = options.fetch(:accessor)
|
174
|
+
reader_visibility = options.fetch(:reader, default_accessor)
|
175
|
+
writer_visibility = options.fetch(:writer, default_accessor)
|
176
|
+
options.update(:reader => reader_visibility, :writer => writer_visibility)
|
177
|
+
end
|
178
|
+
|
179
|
+
end # class Builder
|
180
|
+
|
181
|
+
end # class Attribute
|
182
|
+
end # module Virtus
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Coercer accessor wrapper
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Coercer < Virtus::Coercer
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
attr_reader :method, :coercers
|
11
|
+
|
12
|
+
# Initialize a new coercer object
|
13
|
+
#
|
14
|
+
# @param [Object] coercers accessor
|
15
|
+
# @param [Symbol] coercion method
|
16
|
+
#
|
17
|
+
# @return [undefined]
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def initialize(type, coercers)
|
21
|
+
super(type)
|
22
|
+
@method = type.coercion_method
|
23
|
+
@coercers = coercers
|
24
|
+
end
|
25
|
+
|
26
|
+
# Coerce given value
|
27
|
+
#
|
28
|
+
# @return [Object]
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
def call(value)
|
32
|
+
coercers[value.class].public_send(method, value)
|
33
|
+
rescue ::Coercible::UnsupportedCoercion
|
34
|
+
value
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api public
|
38
|
+
def success?(primitive, value)
|
39
|
+
coercers[primitive].coerced?(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
end # class Coercer
|
43
|
+
|
44
|
+
end # class Attribute
|
45
|
+
end # module Virtus
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Attribute extension providing coercion when setting an attribute value
|
5
|
+
#
|
6
|
+
module Coercible
|
7
|
+
|
8
|
+
# Coerce value before setting
|
9
|
+
#
|
10
|
+
# @see Accessor#set
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
def set(instance, value)
|
14
|
+
super(instance, coerce(value))
|
15
|
+
end
|
16
|
+
|
17
|
+
end # Coercible
|
18
|
+
|
19
|
+
end # Attribute
|
20
|
+
end # Virtus
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Collection attribute handles enumerable-like types
|
5
|
+
#
|
6
|
+
# Handles coercing members to the designated member type.
|
7
|
+
#
|
8
|
+
class Collection < Attribute
|
9
|
+
default Proc.new { |_, attribute| attribute.primitive.new }
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
attr_reader :member_type
|
13
|
+
|
14
|
+
# FIXME: temporary hack, remove when Axiom::Type works with EV as member_type
|
15
|
+
Type = Struct.new(:primitive, :member_type) do
|
16
|
+
def self.infer(type, primitive)
|
17
|
+
return type if axiom_type?(type)
|
18
|
+
|
19
|
+
klass = Axiom::Types.infer(type)
|
20
|
+
member = infer_member_type(type) || Object
|
21
|
+
|
22
|
+
if EmbeddedValue.handles?(member) || pending?(member)
|
23
|
+
Type.new(primitive, member)
|
24
|
+
else
|
25
|
+
klass.new {
|
26
|
+
primitive primitive
|
27
|
+
member_type Axiom::Types.infer(member)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.pending?(primitive)
|
33
|
+
primitive.is_a?(String) || primitive.is_a?(Symbol)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.axiom_type?(type)
|
37
|
+
type.is_a?(Class) && type < Axiom::Types::Type
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.infer_member_type(type)
|
41
|
+
return unless type.respond_to?(:count)
|
42
|
+
|
43
|
+
member_type =
|
44
|
+
if type.count > 1
|
45
|
+
raise NotImplementedError, "build SumType from list of types (#{type})"
|
46
|
+
else
|
47
|
+
type.first
|
48
|
+
end
|
49
|
+
|
50
|
+
if member_type.is_a?(Class) && member_type < Attribute && member_type.primitive
|
51
|
+
member_type.primitive
|
52
|
+
else
|
53
|
+
member_type
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def coercion_method
|
58
|
+
:to_array
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def self.build_type(definition)
|
64
|
+
Type.infer(definition.type, definition.primitive)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
def self.merge_options!(type, options)
|
69
|
+
options[:member_type] ||= Attribute.build(type.member_type, strict: options[:strict])
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api public
|
73
|
+
def coerce(value)
|
74
|
+
coerced = super
|
75
|
+
|
76
|
+
return coerced unless coerced.respond_to?(:each_with_object)
|
77
|
+
|
78
|
+
coerced.each_with_object(primitive.new) do |entry, collection|
|
79
|
+
collection << member_type.coerce(entry)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# @api public
|
84
|
+
def value_coerced?(value)
|
85
|
+
super && value.all? { |item| member_type.value_coerced? item }
|
86
|
+
end
|
87
|
+
|
88
|
+
# @api private
|
89
|
+
def finalize
|
90
|
+
return self if finalized?
|
91
|
+
@member_type = @options[:member_type].finalize
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
def finalized?
|
97
|
+
super && member_type.finalized?
|
98
|
+
end
|
99
|
+
|
100
|
+
end # class Collection
|
101
|
+
|
102
|
+
end # class Attribute
|
103
|
+
end # module Virtus
|