virtus2 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|