virtus2 2.0.1

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