virtus 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|