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.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +1 -0
  5. data/CONTRIBUTING.md +18 -0
  6. data/Changelog.md +258 -0
  7. data/Gemfile +10 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE +20 -0
  10. data/README.md +630 -0
  11. data/Rakefile +15 -0
  12. data/TODO.md +6 -0
  13. data/lib/virtus/attribute/accessor.rb +103 -0
  14. data/lib/virtus/attribute/boolean.rb +55 -0
  15. data/lib/virtus/attribute/builder.rb +182 -0
  16. data/lib/virtus/attribute/coercer.rb +45 -0
  17. data/lib/virtus/attribute/coercible.rb +20 -0
  18. data/lib/virtus/attribute/collection.rb +103 -0
  19. data/lib/virtus/attribute/default_value/from_callable.rb +35 -0
  20. data/lib/virtus/attribute/default_value/from_clonable.rb +35 -0
  21. data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
  22. data/lib/virtus/attribute/default_value.rb +51 -0
  23. data/lib/virtus/attribute/embedded_value.rb +67 -0
  24. data/lib/virtus/attribute/enum.rb +45 -0
  25. data/lib/virtus/attribute/hash.rb +130 -0
  26. data/lib/virtus/attribute/lazy_default.rb +18 -0
  27. data/lib/virtus/attribute/nullify_blank.rb +24 -0
  28. data/lib/virtus/attribute/strict.rb +26 -0
  29. data/lib/virtus/attribute.rb +245 -0
  30. data/lib/virtus/attribute_set.rb +240 -0
  31. data/lib/virtus/builder/hook_context.rb +51 -0
  32. data/lib/virtus/builder.rb +133 -0
  33. data/lib/virtus/class_inclusions.rb +48 -0
  34. data/lib/virtus/class_methods.rb +90 -0
  35. data/lib/virtus/coercer.rb +41 -0
  36. data/lib/virtus/configuration.rb +72 -0
  37. data/lib/virtus/const_missing_extensions.rb +18 -0
  38. data/lib/virtus/extensions.rb +105 -0
  39. data/lib/virtus/instance_methods.rb +218 -0
  40. data/lib/virtus/model.rb +68 -0
  41. data/lib/virtus/module_extensions.rb +88 -0
  42. data/lib/virtus/support/equalizer.rb +128 -0
  43. data/lib/virtus/support/options.rb +113 -0
  44. data/lib/virtus/support/type_lookup.rb +109 -0
  45. data/lib/virtus/value_object.rb +150 -0
  46. data/lib/virtus/version.rb +3 -0
  47. data/lib/virtus.rb +310 -0
  48. data/spec/integration/attributes_attribute_spec.rb +28 -0
  49. data/spec/integration/building_module_spec.rb +90 -0
  50. data/spec/integration/collection_member_coercion_spec.rb +96 -0
  51. data/spec/integration/custom_attributes_spec.rb +42 -0
  52. data/spec/integration/custom_collection_attributes_spec.rb +101 -0
  53. data/spec/integration/default_values_spec.rb +87 -0
  54. data/spec/integration/defining_attributes_spec.rb +86 -0
  55. data/spec/integration/embedded_value_spec.rb +50 -0
  56. data/spec/integration/extending_objects_spec.rb +35 -0
  57. data/spec/integration/hash_attributes_coercion_spec.rb +54 -0
  58. data/spec/integration/inheritance_spec.rb +42 -0
  59. data/spec/integration/injectible_coercers_spec.rb +48 -0
  60. data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
  61. data/spec/integration/overriding_virtus_spec.rb +46 -0
  62. data/spec/integration/required_attributes_spec.rb +25 -0
  63. data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
  64. data/spec/integration/using_modules_spec.rb +55 -0
  65. data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
  66. data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
  67. data/spec/integration/virtus/value_object_spec.rb +99 -0
  68. data/spec/shared/constants_helpers.rb +9 -0
  69. data/spec/shared/freeze_method_behavior.rb +40 -0
  70. data/spec/shared/idempotent_method_behaviour.rb +5 -0
  71. data/spec/shared/options_class_method.rb +19 -0
  72. data/spec/spec_helper.rb +41 -0
  73. data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +43 -0
  74. data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +25 -0
  75. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +180 -0
  76. data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +32 -0
  77. data/spec/unit/virtus/attribute/coerce_spec.rb +129 -0
  78. data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +20 -0
  79. data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +105 -0
  80. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +74 -0
  81. data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
  82. data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
  83. data/spec/unit/virtus/attribute/custom_collection_spec.rb +29 -0
  84. data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
  85. data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +70 -0
  86. data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +91 -0
  87. data/spec/unit/virtus/attribute/get_spec.rb +32 -0
  88. data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +106 -0
  89. data/spec/unit/virtus/attribute/hash/coerce_spec.rb +92 -0
  90. data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +20 -0
  91. data/spec/unit/virtus/attribute/rename_spec.rb +16 -0
  92. data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
  93. data/spec/unit/virtus/attribute/set_default_value_spec.rb +107 -0
  94. data/spec/unit/virtus/attribute/set_spec.rb +29 -0
  95. data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +19 -0
  96. data/spec/unit/virtus/attribute_set/append_spec.rb +47 -0
  97. data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +36 -0
  98. data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +36 -0
  99. data/spec/unit/virtus/attribute_set/each_spec.rb +65 -0
  100. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +17 -0
  101. data/spec/unit/virtus/attribute_set/element_set_spec.rb +64 -0
  102. data/spec/unit/virtus/attribute_set/merge_spec.rb +34 -0
  103. data/spec/unit/virtus/attribute_set/reset_spec.rb +71 -0
  104. data/spec/unit/virtus/attribute_spec.rb +229 -0
  105. data/spec/unit/virtus/attributes_reader_spec.rb +41 -0
  106. data/spec/unit/virtus/attributes_writer_spec.rb +51 -0
  107. data/spec/unit/virtus/class_methods/finalize_spec.rb +67 -0
  108. data/spec/unit/virtus/class_methods/new_spec.rb +39 -0
  109. data/spec/unit/virtus/config_spec.rb +13 -0
  110. data/spec/unit/virtus/element_reader_spec.rb +21 -0
  111. data/spec/unit/virtus/element_writer_spec.rb +19 -0
  112. data/spec/unit/virtus/freeze_spec.rb +41 -0
  113. data/spec/unit/virtus/model_spec.rb +197 -0
  114. data/spec/unit/virtus/module_spec.rb +174 -0
  115. data/spec/unit/virtus/set_default_attributes_spec.rb +32 -0
  116. data/spec/unit/virtus/value_object_spec.rb +138 -0
  117. data/virtus2.gemspec +26 -0
  118. 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