virtus 1.0.1 → 2.0.0
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 +5 -5
- data/.travis.yml +19 -15
- data/Changelog.md +43 -2
- data/Gemfile +5 -5
- data/README.md +113 -78
- data/Rakefile +13 -3
- data/lib/virtus.rb +46 -6
- data/lib/virtus/attribute.rb +21 -3
- data/lib/virtus/attribute/accessor.rb +11 -0
- data/lib/virtus/attribute/builder.rb +8 -13
- data/lib/virtus/attribute/collection.rb +12 -3
- data/lib/virtus/attribute/default_value.rb +2 -0
- data/lib/virtus/attribute/hash.rb +3 -3
- data/lib/virtus/attribute/nullify_blank.rb +24 -0
- data/lib/virtus/attribute/strict.rb +1 -1
- data/lib/virtus/attribute_set.rb +2 -2
- data/lib/virtus/builder.rb +2 -6
- data/lib/virtus/class_inclusions.rb +0 -1
- data/lib/virtus/coercer.rb +1 -0
- data/lib/virtus/configuration.rb +17 -36
- data/lib/virtus/extensions.rb +13 -21
- data/lib/virtus/instance_methods.rb +3 -2
- data/lib/virtus/model.rb +1 -3
- data/lib/virtus/module_extensions.rb +8 -2
- data/lib/virtus/support/equalizer.rb +1 -1
- data/lib/virtus/support/options.rb +2 -1
- data/lib/virtus/support/type_lookup.rb +1 -1
- data/lib/virtus/version.rb +1 -1
- data/spec/integration/attributes_attribute_spec.rb +28 -0
- data/spec/integration/building_module_spec.rb +22 -0
- data/spec/integration/collection_member_coercion_spec.rb +34 -13
- data/spec/integration/custom_attributes_spec.rb +2 -2
- data/spec/integration/custom_collection_attributes_spec.rb +6 -6
- data/spec/integration/default_values_spec.rb +8 -8
- data/spec/integration/defining_attributes_spec.rb +25 -18
- data/spec/integration/embedded_value_spec.rb +5 -5
- data/spec/integration/extending_objects_spec.rb +5 -5
- data/spec/integration/hash_attributes_coercion_spec.rb +16 -12
- data/spec/integration/mass_assignment_with_accessors_spec.rb +5 -5
- data/spec/integration/overriding_virtus_spec.rb +4 -4
- data/spec/integration/required_attributes_spec.rb +1 -1
- data/spec/integration/struct_as_embedded_value_spec.rb +4 -4
- data/spec/integration/using_modules_spec.rb +8 -8
- data/spec/integration/value_object_with_custom_constructor_spec.rb +4 -4
- data/spec/integration/virtus/instance_level_attributes_spec.rb +1 -1
- data/spec/integration/virtus/value_object_spec.rb +14 -14
- data/spec/shared/freeze_method_behavior.rb +6 -3
- data/spec/shared/idempotent_method_behaviour.rb +1 -1
- data/spec/shared/options_class_method.rb +3 -3
- data/spec/spec_helper.rb +2 -18
- data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +3 -3
- data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +3 -3
- data/spec/unit/virtus/attribute/class_methods/build_spec.rb +64 -24
- data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +2 -2
- data/spec/unit/virtus/attribute/coerce_spec.rb +58 -10
- data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +15 -4
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +25 -4
- 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 +8 -2
- data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
- data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +30 -15
- data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +25 -11
- data/spec/unit/virtus/attribute/get_spec.rb +2 -2
- data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +21 -9
- data/spec/unit/virtus/attribute/hash/coerce_spec.rb +9 -9
- data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute/rename_spec.rb +6 -3
- data/spec/unit/virtus/attribute/required_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute/set_default_value_spec.rb +43 -10
- data/spec/unit/virtus/attribute/set_spec.rb +1 -1
- data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +2 -2
- data/spec/unit/virtus/attribute_set/append_spec.rb +6 -6
- data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +12 -11
- data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +13 -12
- data/spec/unit/virtus/attribute_set/each_spec.rb +21 -16
- data/spec/unit/virtus/attribute_set/element_reference_spec.rb +2 -2
- data/spec/unit/virtus/attribute_set/element_set_spec.rb +17 -9
- data/spec/unit/virtus/attribute_set/merge_spec.rb +7 -5
- data/spec/unit/virtus/attribute_set/reset_spec.rb +22 -11
- data/spec/unit/virtus/attribute_spec.rb +8 -7
- data/spec/unit/virtus/attributes_reader_spec.rb +1 -1
- data/spec/unit/virtus/attributes_writer_spec.rb +1 -1
- data/spec/unit/virtus/element_reader_spec.rb +1 -1
- data/spec/unit/virtus/freeze_spec.rb +23 -3
- data/spec/unit/virtus/model_spec.rb +38 -7
- data/spec/unit/virtus/module_spec.rb +59 -2
- data/spec/unit/virtus/set_default_attributes_spec.rb +10 -3
- data/spec/unit/virtus/value_object_spec.rb +15 -5
- data/virtus.gemspec +7 -5
- metadata +46 -44
- data/.ruby-version +0 -1
- data/Gemfile.devtools +0 -54
- data/config/flay.yml +0 -3
- data/config/flog.yml +0 -2
- data/config/mutant.yml +0 -15
- data/config/reek.yml +0 -146
- data/config/yardstick.yml +0 -2
data/lib/virtus/attribute.rb
CHANGED
@@ -13,19 +13,20 @@ module Virtus
|
|
13
13
|
# # strict mode
|
14
14
|
# attr = Virtus::Attribute.build(Integer, :strict => true)
|
15
15
|
# attr.coerce('not really coercible')
|
16
|
-
# # => Virtus::CoercionError: Failed to coerce "
|
16
|
+
# # => Virtus::CoercionError: Failed to coerce "not really coercible" into Integer
|
17
17
|
#
|
18
18
|
class Attribute
|
19
19
|
extend DescendantsTracker, Options, TypeLookup
|
20
20
|
|
21
|
-
include
|
21
|
+
include Equalizer.new(inspect) << :type << :options
|
22
22
|
|
23
|
-
accept_options :primitive, :accessor, :default, :lazy, :strict, :required, :finalize
|
23
|
+
accept_options :primitive, :accessor, :default, :lazy, :strict, :required, :finalize, :nullify_blank
|
24
24
|
|
25
25
|
strict false
|
26
26
|
required true
|
27
27
|
accessor :public
|
28
28
|
finalize true
|
29
|
+
nullify_blank false
|
29
30
|
|
30
31
|
# @see Virtus.coerce
|
31
32
|
#
|
@@ -176,6 +177,23 @@ module Virtus
|
|
176
177
|
kind_of?(Strict)
|
177
178
|
end
|
178
179
|
|
180
|
+
# Return if the attribute is in the nullify blank coercion mode
|
181
|
+
#
|
182
|
+
# @example
|
183
|
+
#
|
184
|
+
# attr = Virtus::Attribute.build(String, :nullify_blank => true)
|
185
|
+
# attr.nullify_blank? # => true
|
186
|
+
#
|
187
|
+
# attr = Virtus::Attribute.build(String, :nullify_blank => false)
|
188
|
+
# attr.nullify_blank? # => false
|
189
|
+
#
|
190
|
+
# @return [Boolean]
|
191
|
+
#
|
192
|
+
# @api public
|
193
|
+
def nullify_blank?
|
194
|
+
kind_of?(NullifyBlank)
|
195
|
+
end
|
196
|
+
|
179
197
|
# Return if the attribute is accepts nil values as valid coercion output
|
180
198
|
#
|
181
199
|
# @example
|
@@ -34,6 +34,17 @@ module Virtus
|
|
34
34
|
descendant.instance_variable_set('@instance_variable_name', "@#{name}")
|
35
35
|
end
|
36
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
|
+
|
37
48
|
# Return value of the attribute
|
38
49
|
#
|
39
50
|
# @param [Object] instance
|
@@ -25,13 +25,7 @@ module Virtus
|
|
25
25
|
# @api private
|
26
26
|
def determine_type
|
27
27
|
if type.include?('::')
|
28
|
-
|
29
|
-
# pick up either Inflecto or ActiveSupport, whateve is available
|
30
|
-
if defined?(Inflecto)
|
31
|
-
Inflecto.constantize(type)
|
32
|
-
else
|
33
|
-
raise NotImplementedError, 'Virtus needs inflecto gem to constantize namespaced constant names'
|
34
|
-
end
|
28
|
+
Virtus.constantize(type)
|
35
29
|
else
|
36
30
|
Object.const_get(type)
|
37
31
|
end
|
@@ -53,7 +47,7 @@ module Virtus
|
|
53
47
|
|
54
48
|
# @api private
|
55
49
|
def pending?
|
56
|
-
@pending
|
50
|
+
@pending if defined?(@pending)
|
57
51
|
end
|
58
52
|
|
59
53
|
private
|
@@ -107,7 +101,7 @@ module Virtus
|
|
107
101
|
determine_type(klass.primitive)
|
108
102
|
elsif EmbeddedValue.handles?(klass)
|
109
103
|
EmbeddedValue
|
110
|
-
elsif klass < Enumerable
|
104
|
+
elsif klass < Enumerable && !(klass <= Range)
|
111
105
|
Collection
|
112
106
|
end
|
113
107
|
end
|
@@ -160,10 +154,11 @@ module Virtus
|
|
160
154
|
def initialize_attribute
|
161
155
|
@attribute = klass.new(type, options)
|
162
156
|
|
163
|
-
@attribute.extend(Accessor)
|
164
|
-
@attribute.extend(Coercible)
|
165
|
-
@attribute.extend(
|
166
|
-
@attribute.extend(
|
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]
|
167
162
|
|
168
163
|
@attribute.finalize if options[:finalize]
|
169
164
|
end
|
@@ -66,16 +66,25 @@ module Virtus
|
|
66
66
|
|
67
67
|
# @api private
|
68
68
|
def self.merge_options!(type, options)
|
69
|
-
options[:member_type] ||= Attribute.build(type.member_type)
|
69
|
+
options[:member_type] ||= Attribute.build(type.member_type, strict: options[:strict])
|
70
70
|
end
|
71
71
|
|
72
72
|
# @api public
|
73
|
-
def coerce(
|
74
|
-
|
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|
|
75
79
|
collection << member_type.coerce(entry)
|
76
80
|
end
|
77
81
|
end
|
78
82
|
|
83
|
+
# @api public
|
84
|
+
def value_coerced?(value)
|
85
|
+
super && value.all? { |item| member_type.value_coerced? item }
|
86
|
+
end
|
87
|
+
|
79
88
|
# @api private
|
80
89
|
def finalize
|
81
90
|
return self if finalized?
|
@@ -70,7 +70,7 @@ module Virtus
|
|
70
70
|
value_type
|
71
71
|
end
|
72
72
|
|
73
|
-
{ :key_type
|
73
|
+
{ :key_type => key_primitive, :value_type => value_primitive}
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -92,8 +92,8 @@ module Virtus
|
|
92
92
|
|
93
93
|
# @api private
|
94
94
|
def self.merge_options!(type, options)
|
95
|
-
options[:key_type] ||= Attribute.build(type.key_type)
|
96
|
-
options[:value_type] ||= Attribute.build(type.value_type)
|
95
|
+
options[:key_type] ||= Attribute.build(type.key_type, :strict => options[:strict])
|
96
|
+
options[:value_type] ||= Attribute.build(type.value_type, :strict => options[:strict])
|
97
97
|
end
|
98
98
|
|
99
99
|
# Coerce members
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Attribute extension which nullifies blank attributes when coercion failed
|
5
|
+
#
|
6
|
+
module NullifyBlank
|
7
|
+
|
8
|
+
# @see [Attribute#coerce]
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
def coerce(input)
|
12
|
+
output = super
|
13
|
+
|
14
|
+
if !value_coerced?(output) && input.to_s.empty?
|
15
|
+
nil
|
16
|
+
else
|
17
|
+
output
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end # NullifyBlank
|
22
|
+
|
23
|
+
end # Attribute
|
24
|
+
end # Virtus
|
data/lib/virtus/attribute_set.rb
CHANGED
@@ -43,7 +43,7 @@ module Virtus
|
|
43
43
|
# @api public
|
44
44
|
def each
|
45
45
|
return to_enum unless block_given?
|
46
|
-
@index.
|
46
|
+
@index.each { |name, attribute| yield attribute if name.kind_of?(Symbol) }
|
47
47
|
self
|
48
48
|
end
|
49
49
|
|
@@ -209,7 +209,7 @@ module Virtus
|
|
209
209
|
|
210
210
|
# @api private
|
211
211
|
def skip_default?(object, attribute)
|
212
|
-
attribute.lazy? ||
|
212
|
+
attribute.lazy? || attribute.defined?(object)
|
213
213
|
end
|
214
214
|
|
215
215
|
# Merge the attributes into the index
|
data/lib/virtus/builder.rb
CHANGED
@@ -25,7 +25,7 @@ module Virtus
|
|
25
25
|
|
26
26
|
# @api private
|
27
27
|
def self.call(options, &block)
|
28
|
-
new(Configuration.
|
28
|
+
new(Configuration.new(options, &block)).mod
|
29
29
|
end
|
30
30
|
|
31
31
|
# @api private
|
@@ -120,11 +120,7 @@ module Virtus
|
|
120
120
|
|
121
121
|
# @api private
|
122
122
|
def extensions
|
123
|
-
super
|
124
|
-
Extensions::AllowedWriterMethods,
|
125
|
-
ValueObject::AllowedWriterMethods,
|
126
|
-
ValueObject::InstanceMethods
|
127
|
-
]
|
123
|
+
super << ValueObject::AllowedWriterMethods << ValueObject::InstanceMethods
|
128
124
|
end
|
129
125
|
|
130
126
|
# @api private
|
@@ -13,7 +13,6 @@ module Virtus
|
|
13
13
|
def self.included(descendant)
|
14
14
|
super
|
15
15
|
descendant.extend(ClassMethods)
|
16
|
-
descendant.extend(Extensions::AllowedWriterMethods)
|
17
16
|
descendant.class_eval { include Methods }
|
18
17
|
descendant.class_eval { include InstanceMethods }
|
19
18
|
descendant.class_eval { include InstanceMethods::Constructor }
|
data/lib/virtus/coercer.rb
CHANGED
data/lib/virtus/configuration.rb
CHANGED
@@ -12,55 +12,34 @@ module Virtus
|
|
12
12
|
# Access the strict setting for this instance
|
13
13
|
attr_accessor :strict
|
14
14
|
|
15
|
+
# Access the nullify_blank setting for this instance
|
16
|
+
attr_accessor :nullify_blank
|
17
|
+
|
18
|
+
# Access the required setting for this instance
|
19
|
+
attr_accessor :required
|
20
|
+
|
15
21
|
# Access the constructor setting for this instance
|
16
22
|
attr_accessor :constructor
|
17
23
|
|
18
24
|
# Access the mass-assignment setting for this instance
|
19
25
|
attr_accessor :mass_assignment
|
20
26
|
|
21
|
-
# Build new configuration instance using the passed block
|
22
|
-
#
|
23
|
-
# @example
|
24
|
-
# Configuration.build do |config|
|
25
|
-
# config.coerce = false
|
26
|
-
# end
|
27
|
-
#
|
28
|
-
# @return [Configuration]
|
29
|
-
#
|
30
|
-
# @api public
|
31
|
-
def self.build(options = {}, &block)
|
32
|
-
config = new.call(&block)
|
33
|
-
options.each { |key, value| config.public_send("#{key}=", value) }
|
34
|
-
config
|
35
|
-
end
|
36
|
-
|
37
27
|
# Initialized a configuration instance
|
38
28
|
#
|
39
29
|
# @return [undefined]
|
40
30
|
#
|
41
31
|
# @api private
|
42
|
-
def initialize
|
43
|
-
@finalize = true
|
44
|
-
@coerce = true
|
45
|
-
@strict = false
|
46
|
-
@
|
47
|
-
@
|
32
|
+
def initialize(options={})
|
33
|
+
@finalize = options.fetch(:finalize, true)
|
34
|
+
@coerce = options.fetch(:coerce, true)
|
35
|
+
@strict = options.fetch(:strict, false)
|
36
|
+
@nullify_blank = options.fetch(:nullify_blank, false)
|
37
|
+
@required = options.fetch(:required, true)
|
38
|
+
@constructor = options.fetch(:constructor, true)
|
39
|
+
@mass_assignment = options.fetch(:mass_assignment, true)
|
48
40
|
@coercer = Coercible::Coercer.new
|
49
|
-
end
|
50
41
|
|
51
|
-
|
52
|
-
#
|
53
|
-
# @example
|
54
|
-
# configuration.call do |config|
|
55
|
-
# config.coerce = false
|
56
|
-
# end
|
57
|
-
#
|
58
|
-
# @return [self]
|
59
|
-
#
|
60
|
-
# @api private
|
61
|
-
def call(&block)
|
62
|
-
block.call(self) if block_given?
|
63
|
-
self
|
42
|
+
yield self if block_given?
|
64
43
|
end
|
65
44
|
|
66
45
|
# Access the coercer for this instance and optional configure a
|
@@ -84,6 +63,8 @@ module Virtus
|
|
84
63
|
{ :coerce => coerce,
|
85
64
|
:finalize => finalize,
|
86
65
|
:strict => strict,
|
66
|
+
:nullify_blank => nullify_blank,
|
67
|
+
:required => required,
|
87
68
|
:configured_coercer => coercer }.freeze
|
88
69
|
end
|
89
70
|
|
data/lib/virtus/extensions.rb
CHANGED
@@ -18,7 +18,6 @@ module Virtus
|
|
18
18
|
object.instance_eval do
|
19
19
|
extend Methods
|
20
20
|
extend InstanceMethods
|
21
|
-
extend AllowedWriterMethods
|
22
21
|
extend InstanceMethods::MassAssignment
|
23
22
|
end
|
24
23
|
end
|
@@ -72,26 +71,9 @@ module Virtus
|
|
72
71
|
def values(&block)
|
73
72
|
private :attributes= if instance_methods.include?(:attributes=)
|
74
73
|
yield
|
75
|
-
include(
|
74
|
+
include(Equalizer.new(name, attribute_set.map(&:name)))
|
76
75
|
end
|
77
76
|
|
78
|
-
private
|
79
|
-
|
80
|
-
# Return an attribute set for that instance
|
81
|
-
#
|
82
|
-
# @return [AttributeSet]
|
83
|
-
#
|
84
|
-
# @api private
|
85
|
-
def attribute_set
|
86
|
-
@attribute_set
|
87
|
-
end
|
88
|
-
|
89
|
-
end # Methods
|
90
|
-
|
91
|
-
module AllowedWriterMethods
|
92
|
-
WRITER_METHOD_REGEXP = /=\z/.freeze
|
93
|
-
INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze
|
94
|
-
|
95
77
|
# The list of writer methods that can be mass-assigned to in #attributes=
|
96
78
|
#
|
97
79
|
# @return [Set]
|
@@ -106,8 +88,18 @@ module Virtus
|
|
106
88
|
end
|
107
89
|
end
|
108
90
|
|
109
|
-
|
91
|
+
private
|
110
92
|
|
111
|
-
|
93
|
+
# Return an attribute set for that instance
|
94
|
+
#
|
95
|
+
# @return [AttributeSet]
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
def attribute_set
|
99
|
+
@attribute_set
|
100
|
+
end
|
112
101
|
|
102
|
+
end # Methods
|
103
|
+
|
104
|
+
end # module Extensions
|
113
105
|
end # module Virtus
|
@@ -14,7 +14,7 @@ module Virtus
|
|
14
14
|
#
|
15
15
|
# @api private
|
16
16
|
def initialize(attributes = nil)
|
17
|
-
|
17
|
+
attribute_set.set(self, attributes) if attributes
|
18
18
|
set_default_attributes
|
19
19
|
end
|
20
20
|
|
@@ -42,6 +42,7 @@ module Virtus
|
|
42
42
|
attribute_set.get(self)
|
43
43
|
end
|
44
44
|
alias_method :to_hash, :attributes
|
45
|
+
alias_method :to_h, :attributes
|
45
46
|
|
46
47
|
# Mass-assign attribute values
|
47
48
|
#
|
@@ -191,7 +192,7 @@ module Virtus
|
|
191
192
|
#
|
192
193
|
# @api public
|
193
194
|
def set_default_attributes!
|
194
|
-
attribute_set.set_defaults(self, proc { |
|
195
|
+
attribute_set.set_defaults(self, proc { |object, attribute| attribute.defined?(object) })
|
195
196
|
self
|
196
197
|
end
|
197
198
|
|
data/lib/virtus/model.rb
CHANGED
@@ -31,7 +31,7 @@ module Virtus
|
|
31
31
|
descendant.extend(Extensions::Methods)
|
32
32
|
descendant.extend(InstanceMethods)
|
33
33
|
end
|
34
|
-
private_class_method :
|
34
|
+
private_class_method :extended
|
35
35
|
|
36
36
|
end # Core
|
37
37
|
|
@@ -51,7 +51,6 @@ module Virtus
|
|
51
51
|
# @api private
|
52
52
|
def self.included(descendant)
|
53
53
|
super
|
54
|
-
descendant.extend(Extensions::AllowedWriterMethods)
|
55
54
|
descendant.send(:include, InstanceMethods::MassAssignment)
|
56
55
|
end
|
57
56
|
private_class_method :included
|
@@ -59,7 +58,6 @@ module Virtus
|
|
59
58
|
# @api private
|
60
59
|
def self.extended(descendant)
|
61
60
|
super
|
62
|
-
descendant.extend(Extensions::AllowedWriterMethods)
|
63
61
|
descendant.extend(InstanceMethods::MassAssignment)
|
64
62
|
end
|
65
63
|
private_class_method :extended
|