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
@@ -0,0 +1,35 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
class DefaultValue
|
4
|
+
|
5
|
+
# Represents default value evaluated via a callable object
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class FromCallable < DefaultValue
|
9
|
+
|
10
|
+
# Return if the class can handle the value
|
11
|
+
#
|
12
|
+
# @param [Object] value
|
13
|
+
#
|
14
|
+
# @return [Boolean]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def self.handle?(value)
|
18
|
+
value.respond_to?(:call)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Evaluates the value via value#call
|
22
|
+
#
|
23
|
+
# @param [Object] args
|
24
|
+
#
|
25
|
+
# @return [Object] evaluated value
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
def call(*args)
|
29
|
+
@value.call(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class FromCallable
|
33
|
+
end # class DefaultValue
|
34
|
+
end # class Attribute
|
35
|
+
end # module Virtus
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
class DefaultValue
|
4
|
+
|
5
|
+
# Represents default value evaluated via a clonable object
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class FromClonable < DefaultValue
|
9
|
+
SINGLETON_CLASSES = [
|
10
|
+
::NilClass, ::TrueClass, ::FalseClass, ::Numeric, ::Symbol ].freeze
|
11
|
+
|
12
|
+
# Return if the class can handle the value
|
13
|
+
#
|
14
|
+
# @param [Object] value
|
15
|
+
#
|
16
|
+
# @return [Boolean]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
def self.handle?(value)
|
20
|
+
SINGLETON_CLASSES.none? { |klass| value.kind_of?(klass) }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Evaluates the value via value#clone
|
24
|
+
#
|
25
|
+
# @return [Object] evaluated value
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
def call(*)
|
29
|
+
@value.clone
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class FromClonable
|
33
|
+
end # class DefaultValue
|
34
|
+
end # class Attribute
|
35
|
+
end # module Virtus
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
class DefaultValue
|
4
|
+
|
5
|
+
# Represents default value evaluated via a symbol
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class FromSymbol < DefaultValue
|
9
|
+
|
10
|
+
# Return if the class can handle the value
|
11
|
+
#
|
12
|
+
# @param [Object] value
|
13
|
+
#
|
14
|
+
# @return [Boolean]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def self.handle?(value)
|
18
|
+
value.is_a?(Symbol)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Evaluates the value via instance#public_send(value)
|
22
|
+
#
|
23
|
+
# Symbol value is returned if the instance doesn't respond to value
|
24
|
+
#
|
25
|
+
# @return [Object] evaluated value
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
def call(instance, _)
|
29
|
+
instance.respond_to?(@value, true) ? instance.send(@value) : @value
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class FromSymbol
|
33
|
+
end # class DefaultValue
|
34
|
+
end # class Attribute
|
35
|
+
end # module Virtus
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Class representing the default value option
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class DefaultValue
|
8
|
+
extend DescendantsTracker
|
9
|
+
|
10
|
+
include Equalizer.new(inspect) << :value
|
11
|
+
|
12
|
+
# Builds a default value instance
|
13
|
+
#
|
14
|
+
# @return [Virtus::Attribute::DefaultValue]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
def self.build(*args)
|
18
|
+
klass = descendants.detect { |descendant| descendant.handle?(*args) } || self
|
19
|
+
klass.new(*args)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the value instance
|
23
|
+
#
|
24
|
+
# @return [Object]
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
attr_reader :value
|
28
|
+
|
29
|
+
# Initializes an default value instance
|
30
|
+
#
|
31
|
+
# @param [Object] value
|
32
|
+
#
|
33
|
+
# @return [undefined]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def initialize(value)
|
37
|
+
@value = value
|
38
|
+
end
|
39
|
+
|
40
|
+
# Evaluates the value
|
41
|
+
#
|
42
|
+
# @return [Object] evaluated value
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def call(*)
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
end # class DefaultValue
|
50
|
+
end # class Attribute
|
51
|
+
end # module Virtus
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# EmbeddedValue handles virtus-like objects, OpenStruct and Struct
|
5
|
+
#
|
6
|
+
class EmbeddedValue < Attribute
|
7
|
+
TYPES = [Struct, OpenStruct, Virtus, Model::Constructor].freeze
|
8
|
+
|
9
|
+
# Builds Struct-like instance with attributes passed to the constructor as
|
10
|
+
# a list of args rather than a hash
|
11
|
+
#
|
12
|
+
# @private
|
13
|
+
class FromStruct < Virtus::Coercer
|
14
|
+
|
15
|
+
# @api public
|
16
|
+
def call(input)
|
17
|
+
if input.kind_of?(primitive)
|
18
|
+
input
|
19
|
+
elsif not input.nil?
|
20
|
+
primitive.new(*input)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end # FromStruct
|
25
|
+
|
26
|
+
# Builds OpenStruct-like instance with attributes passed to the constructor
|
27
|
+
# as a hash
|
28
|
+
#
|
29
|
+
# @private
|
30
|
+
class FromOpenStruct < Virtus::Coercer
|
31
|
+
|
32
|
+
# @api public
|
33
|
+
def call(input)
|
34
|
+
if input.kind_of?(primitive)
|
35
|
+
input
|
36
|
+
elsif not input.nil?
|
37
|
+
primitive.new(input)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end # FromOpenStruct
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def self.handles?(klass)
|
45
|
+
klass.is_a?(Class) && TYPES.any? { |type| klass <= type }
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
def self.build_type(definition)
|
50
|
+
Axiom::Types::Object.new { primitive definition.primitive }
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def self.build_coercer(type, _options)
|
55
|
+
primitive = type.primitive
|
56
|
+
|
57
|
+
if primitive < Virtus || primitive < Model::Constructor || primitive <= OpenStruct
|
58
|
+
FromOpenStruct.new(type)
|
59
|
+
elsif primitive < Struct
|
60
|
+
FromStruct.new(type)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end # class EmbeddedValue
|
65
|
+
|
66
|
+
end # class Attribute
|
67
|
+
end # module Virtus
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Virtus
|
4
|
+
class Attribute
|
5
|
+
|
6
|
+
# Type to be used inside attributes as enums.
|
7
|
+
# @example
|
8
|
+
# class Event < Quiver::BaseModel
|
9
|
+
# attribute :mode, Quiver::Types::Enum[:read, :write]
|
10
|
+
# end
|
11
|
+
class Enum < Attribute
|
12
|
+
|
13
|
+
primitive Axiom::Types::Symbol
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
attr_accessor :values
|
18
|
+
|
19
|
+
# @param values [Array<Symbol>]
|
20
|
+
def [](*values)
|
21
|
+
klass = Class.new(self)
|
22
|
+
klass.values = values
|
23
|
+
klass
|
24
|
+
end
|
25
|
+
|
26
|
+
# :nodoc:
|
27
|
+
def build_type(*)
|
28
|
+
Axiom::Types::Symbol
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def coercion_error_message
|
34
|
+
"Enum (#{self.class.values.join(',')})"
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param value [Object]
|
38
|
+
# @return [Boolean]
|
39
|
+
def value_coerced?(value)
|
40
|
+
self.class.values.include?(value)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Handles attributes with Hash type
|
5
|
+
#
|
6
|
+
class Hash < Attribute
|
7
|
+
primitive ::Hash
|
8
|
+
default primitive.new
|
9
|
+
|
10
|
+
# @api private
|
11
|
+
attr_reader :key_type, :value_type
|
12
|
+
|
13
|
+
# FIXME: remove this once axiom-types supports it
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
Type = Struct.new(:key_type, :value_type) do
|
17
|
+
def self.infer(type)
|
18
|
+
if axiom_type?(type)
|
19
|
+
new(type.key_type, type.value_type)
|
20
|
+
else
|
21
|
+
type_options = infer_key_and_value_types(type)
|
22
|
+
key_class = determine_type(type_options.fetch(:key_type, Object))
|
23
|
+
value_class = determine_type(type_options.fetch(:value_type, Object))
|
24
|
+
|
25
|
+
new(key_class, value_class)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def self.pending?(primitive)
|
31
|
+
primitive.is_a?(String) || primitive.is_a?(Symbol)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @api private
|
35
|
+
def self.axiom_type?(type)
|
36
|
+
type.is_a?(Class) && type < Axiom::Types::Type
|
37
|
+
end
|
38
|
+
|
39
|
+
# @api private
|
40
|
+
def self.determine_type(type)
|
41
|
+
return type if pending?(type)
|
42
|
+
|
43
|
+
if EmbeddedValue.handles?(type)
|
44
|
+
type
|
45
|
+
else
|
46
|
+
Axiom::Types.infer(type)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def self.infer_key_and_value_types(type)
|
52
|
+
return {} unless type.kind_of?(::Hash)
|
53
|
+
|
54
|
+
if type.size > 1
|
55
|
+
raise ArgumentError, "more than one [key => value] pair in `#{type}`"
|
56
|
+
else
|
57
|
+
key_type, value_type = type.keys.first, type.values.first
|
58
|
+
|
59
|
+
key_primitive =
|
60
|
+
if key_type.is_a?(Class) && key_type < Attribute && key_type.primitive
|
61
|
+
key_type.primitive
|
62
|
+
else
|
63
|
+
key_type
|
64
|
+
end
|
65
|
+
|
66
|
+
value_primitive =
|
67
|
+
if value_type.is_a?(Class) && value_type < Attribute && value_type.primitive
|
68
|
+
value_type.primitive
|
69
|
+
else
|
70
|
+
value_type
|
71
|
+
end
|
72
|
+
|
73
|
+
{ :key_type => key_primitive, :value_type => value_primitive}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @api private
|
78
|
+
def coercion_method
|
79
|
+
:to_hash
|
80
|
+
end
|
81
|
+
|
82
|
+
# @api private
|
83
|
+
def primitive
|
84
|
+
::Hash
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# @api private
|
89
|
+
def self.build_type(definition)
|
90
|
+
Type.infer(definition.type)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @api private
|
94
|
+
def self.merge_options!(type, options)
|
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
|
+
end
|
98
|
+
|
99
|
+
# Coerce members
|
100
|
+
#
|
101
|
+
# @see [Attribute#coerce]
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def coerce(*)
|
105
|
+
coerced = super
|
106
|
+
|
107
|
+
return coerced unless coerced.respond_to?(:each_with_object)
|
108
|
+
|
109
|
+
coerced.each_with_object({}) do |(key, value), hash|
|
110
|
+
hash[key_type.coerce(key)] = value_type.coerce(value)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @api private
|
115
|
+
def finalize
|
116
|
+
return self if finalized?
|
117
|
+
@key_type = options[:key_type].finalize
|
118
|
+
@value_type = options[:value_type].finalize
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
def finalized?
|
124
|
+
super && key_type.finalized? && value_type.finalized?
|
125
|
+
end
|
126
|
+
|
127
|
+
end # class Hash
|
128
|
+
|
129
|
+
end # class Attribute
|
130
|
+
end # module Virtus
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
module LazyDefault
|
5
|
+
|
6
|
+
# @api public
|
7
|
+
def get(instance)
|
8
|
+
if instance.instance_variable_defined?(instance_variable_name)
|
9
|
+
super
|
10
|
+
else
|
11
|
+
set_default_value(instance)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end # LazyDefault
|
16
|
+
|
17
|
+
end # Attribute
|
18
|
+
end # Virtus
|
@@ -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
|