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,105 @@
1
+ module Virtus
2
+
3
+ # Extensions common for both classes and instances
4
+ module Extensions
5
+ WRITER_METHOD_REGEXP = /=\z/.freeze
6
+ INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze
7
+ RESERVED_NAMES = [:attributes].to_set.freeze
8
+
9
+ # A hook called when an object is extended with Virtus
10
+ #
11
+ # @param [Object] object
12
+ #
13
+ # @return [undefined]
14
+ #
15
+ # @api private
16
+ def self.extended(object)
17
+ super
18
+ object.instance_eval do
19
+ extend Methods
20
+ extend InstanceMethods
21
+ extend InstanceMethods::MassAssignment
22
+ end
23
+ end
24
+ private_class_method :extended
25
+
26
+ module Methods
27
+
28
+ # @api private
29
+ def self.extended(descendant)
30
+ super
31
+ descendant.extend(AttributeSet.create(descendant))
32
+ end
33
+ private_class_method :extended
34
+
35
+ # Defines an attribute on an object's class or instance
36
+ #
37
+ # @example
38
+ # class Book
39
+ # include Virtus.model
40
+ #
41
+ # attribute :title, String
42
+ # attribute :author, String
43
+ # attribute :published_at, DateTime
44
+ # attribute :page_count, Integer
45
+ # attribute :index # defaults to Object
46
+ # end
47
+ #
48
+ # @param [Symbol] name
49
+ # the name of an attribute
50
+ #
51
+ # @param [Class,Array,Hash,Axiom::Types::Type,String,Symbol] type
52
+ # the type class of an attribute
53
+ #
54
+ # @param [#to_hash] options
55
+ # the extra options hash
56
+ #
57
+ # @return [self]
58
+ #
59
+ # @see Attribute.build
60
+ #
61
+ # @api public
62
+ def attribute(name, type = nil, options = {})
63
+ assert_valid_name(name)
64
+ attribute_set << Attribute.build(type, options.merge(:name => name))
65
+ self
66
+ end
67
+
68
+ # @see Virtus.default_value
69
+ #
70
+ # @api public
71
+ def values(&block)
72
+ private :attributes= if instance_methods.include?(:attributes=)
73
+ yield
74
+ include(Equalizer.new(name, attribute_set.map(&:name)))
75
+ end
76
+
77
+ # The list of writer methods that can be mass-assigned to in #attributes=
78
+ #
79
+ # @return [Set]
80
+ #
81
+ # @api private
82
+ def allowed_writer_methods
83
+ @allowed_writer_methods ||=
84
+ begin
85
+ allowed_writer_methods = allowed_methods.grep(WRITER_METHOD_REGEXP).to_set
86
+ allowed_writer_methods -= INVALID_WRITER_METHODS
87
+ allowed_writer_methods.freeze
88
+ end
89
+ end
90
+
91
+ private
92
+
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
101
+
102
+ end # Methods
103
+
104
+ end # module Extensions
105
+ end # module Virtus
@@ -0,0 +1,218 @@
1
+ module Virtus
2
+
3
+ # Instance methods that are added when you include Virtus
4
+ module InstanceMethods
5
+
6
+ module Constructor
7
+
8
+ # Set attributes during initialization of an object
9
+ #
10
+ # @param [#to_hash] attributes
11
+ # the attributes hash to be set
12
+ #
13
+ # @return [undefined]
14
+ #
15
+ # @api private
16
+ def initialize(attributes = nil)
17
+ attribute_set.set(self, attributes) if attributes
18
+ set_default_attributes
19
+ end
20
+
21
+ end # Constructor
22
+
23
+ module MassAssignment
24
+
25
+ # Returns a hash of all publicly accessible attributes
26
+ #
27
+ # @example
28
+ # class User
29
+ # include Virtus
30
+ #
31
+ # attribute :name, String
32
+ # attribute :age, Integer
33
+ # end
34
+ #
35
+ # user = User.new(:name => 'John', :age => 28)
36
+ # user.attributes # => { :name => 'John', :age => 28 }
37
+ #
38
+ # @return [Hash]
39
+ #
40
+ # @api public
41
+ def attributes
42
+ attribute_set.get(self)
43
+ end
44
+ alias_method :to_hash, :attributes
45
+ alias_method :to_h, :attributes
46
+
47
+ # Mass-assign attribute values
48
+ #
49
+ # Keys in the +attributes+ param can be symbols or strings.
50
+ # All referenced Attribute writer methods *will* be called.
51
+ # Non-attribute setter methods on the receiver *will* be called.
52
+ #
53
+ # @example
54
+ # class User
55
+ # include Virtus
56
+ #
57
+ # attribute :name, String
58
+ # attribute :age, Integer
59
+ # end
60
+ #
61
+ # user = User.new
62
+ # user.attributes = { :name => 'John', 'age' => 28 }
63
+ #
64
+ # @param [#to_hash] attributes
65
+ # a hash of attribute names and values to set on the receiver
66
+ #
67
+ # @return [Hash]
68
+ #
69
+ # @api public
70
+ def attributes=(attributes)
71
+ attribute_set.set(self, attributes)
72
+ end
73
+
74
+ end # MassAssignment
75
+
76
+ # Returns a value of the attribute with the given name
77
+ #
78
+ # @example
79
+ # class User
80
+ # include Virtus
81
+ #
82
+ # attribute :name, String
83
+ # end
84
+ #
85
+ # user = User.new(:name => 'John')
86
+ # user[:name] # => "John"
87
+ #
88
+ # @param [Symbol] name
89
+ # a name of an attribute
90
+ #
91
+ # @return [Object]
92
+ # a value of an attribute
93
+ #
94
+ # @api public
95
+ def [](name)
96
+ public_send(name)
97
+ end
98
+
99
+ # Sets a value of the attribute with the given name
100
+ #
101
+ # @example
102
+ # class User
103
+ # include Virtus
104
+ #
105
+ # attribute :name, String
106
+ # end
107
+ #
108
+ # user = User.new
109
+ # user[:name] = "John" # => "John"
110
+ # user.name # => "John"
111
+ #
112
+ # @param [Symbol] name
113
+ # a name of an attribute
114
+ #
115
+ # @param [Object] value
116
+ # a value to be set
117
+ #
118
+ # @return [Object]
119
+ # the value set on an object
120
+ #
121
+ # @api public
122
+ def []=(name, value)
123
+ public_send("#{name}=", value)
124
+ end
125
+
126
+ # Freeze object
127
+ #
128
+ # @return [self]
129
+ #
130
+ # @api public
131
+ #
132
+ # @example
133
+ #
134
+ # class User
135
+ # include Virtus
136
+ #
137
+ # attribute :name, String
138
+ # attribute :age, Integer
139
+ # end
140
+ #
141
+ # user = User.new(:name => 'John', :age => 28)
142
+ # user.frozen? # => false
143
+ # user.freeze
144
+ # user.frozen? # => true
145
+ #
146
+ # @api public
147
+ def freeze
148
+ set_default_attributes!
149
+ super
150
+ end
151
+
152
+ # Reset an attribute to its default
153
+ #
154
+ # @return [self]
155
+ #
156
+ # @api public
157
+ #
158
+ # @example
159
+ #
160
+ # class User
161
+ # include Virtus
162
+ #
163
+ # attribute :age, Integer, default: 21
164
+ # end
165
+ #
166
+ # user = User.new(:name => 'John', :age => 28)
167
+ # user.age = 30
168
+ # user.age # => 30
169
+ # user.reset_attribute(:age)
170
+ # user.age # => 21
171
+ #
172
+ # @api public
173
+ def reset_attribute(attribute_name)
174
+ attribute = attribute_set[attribute_name]
175
+ attribute.set_default_value(self) if attribute
176
+ self
177
+ end
178
+
179
+ # Set default attributes
180
+ #
181
+ # @return [self]
182
+ #
183
+ # @api private
184
+ def set_default_attributes
185
+ attribute_set.set_defaults(self)
186
+ self
187
+ end
188
+
189
+ # Set default attributes even lazy ones
190
+ #
191
+ # @return [self]
192
+ #
193
+ # @api public
194
+ def set_default_attributes!
195
+ attribute_set.set_defaults(self, proc { |object, attribute| attribute.defined?(object) })
196
+ self
197
+ end
198
+
199
+ private
200
+
201
+ # The list of allowed public methods
202
+ #
203
+ # @return [Array<String>]
204
+ #
205
+ # @api private
206
+ def allowed_methods
207
+ public_methods.map(&:to_s)
208
+ end
209
+
210
+ # @api private
211
+ def assert_valid_name(name)
212
+ if respond_to?(:attributes) && name.to_sym == :attributes || name.to_sym == :attribute_set
213
+ raise ArgumentError, "#{name.inspect} is not allowed as an attribute name"
214
+ end
215
+ end
216
+
217
+ end # module InstanceMethods
218
+ end # module Virtus
@@ -0,0 +1,68 @@
1
+ module Virtus
2
+
3
+ module Model
4
+
5
+ # @api private
6
+ def self.included(descendant)
7
+ super
8
+ descendant.send(:include, ClassInclusions)
9
+ end
10
+
11
+ # @api private
12
+ def self.extended(descendant)
13
+ super
14
+ descendant.extend(Extensions)
15
+ end
16
+
17
+ module Core
18
+
19
+ # @api private
20
+ def self.included(descendant)
21
+ super
22
+ descendant.extend(ClassMethods)
23
+ descendant.send(:include, ClassInclusions::Methods)
24
+ descendant.send(:include, InstanceMethods)
25
+ end
26
+ private_class_method :included
27
+
28
+ # @api private
29
+ def self.extended(descendant)
30
+ super
31
+ descendant.extend(Extensions::Methods)
32
+ descendant.extend(InstanceMethods)
33
+ end
34
+ private_class_method :extended
35
+
36
+ end # Core
37
+
38
+ module Constructor
39
+
40
+ # @api private
41
+ def self.included(descendant)
42
+ super
43
+ descendant.send(:include, InstanceMethods::Constructor)
44
+ end
45
+ private_class_method :included
46
+
47
+ end # Constructor
48
+
49
+ module MassAssignment
50
+
51
+ # @api private
52
+ def self.included(descendant)
53
+ super
54
+ descendant.send(:include, InstanceMethods::MassAssignment)
55
+ end
56
+ private_class_method :included
57
+
58
+ # @api private
59
+ def self.extended(descendant)
60
+ super
61
+ descendant.extend(InstanceMethods::MassAssignment)
62
+ end
63
+ private_class_method :extended
64
+
65
+ end # MassAssignment
66
+
67
+ end # Model
68
+ end # Virtus
@@ -0,0 +1,88 @@
1
+ module Virtus
2
+
3
+ # Virtus module that can define attributes for later inclusion
4
+ #
5
+ # @private
6
+ module ModuleExtensions
7
+ include ConstMissingExtensions
8
+
9
+ # @api private
10
+ def self.extended(mod)
11
+ super
12
+ setup(mod)
13
+ end
14
+
15
+ # @api private
16
+ def self.setup(mod, inclusions = [Model], attribute_definitions = [])
17
+ mod.instance_variable_set('@inclusions', inclusions)
18
+ existing_attributes = mod.instance_variable_get('@attribute_definitions')
19
+ new_attributes = (existing_attributes || []) + attribute_definitions
20
+ mod.instance_variable_set('@attribute_definitions', new_attributes)
21
+ end
22
+
23
+ # Define an attribute in the module
24
+ #
25
+ # @see Virtus::Extensions#attribute
26
+ #
27
+ # @return [self]
28
+ #
29
+ # @api private
30
+ def attribute(name, type = nil, options = {})
31
+ @attribute_definitions << [name, type, options]
32
+ self
33
+ end
34
+
35
+ private
36
+
37
+ # Extend an object with Virtus methods and define attributes
38
+ #
39
+ # @param [Object] object
40
+ #
41
+ # @return [undefined]
42
+ #
43
+ # @api private
44
+ def extended(object)
45
+ super
46
+ @inclusions.each { |mod| object.extend(mod) }
47
+ define_attributes(object)
48
+ object.set_default_attributes
49
+ end
50
+
51
+ # Extend a class with Virtus methods and define attributes
52
+ #
53
+ # @param [Object] object
54
+ #
55
+ # @return [undefined]
56
+ #
57
+ # @api private
58
+ def included(object)
59
+ super
60
+
61
+ if Class === object
62
+ @inclusions.reject do |mod|
63
+ object.ancestors.include?(mod)
64
+ end.each do |mod|
65
+ object.send(:include, mod)
66
+ end
67
+ define_attributes(object)
68
+ else
69
+ object.extend(ModuleExtensions)
70
+ ModuleExtensions.setup(object, @inclusions, @attribute_definitions)
71
+ end
72
+ end
73
+
74
+ # Define attributes on a class or instance
75
+ #
76
+ # @param [Object,Class] object
77
+ #
78
+ # @return [undefined]
79
+ #
80
+ # @api private
81
+ def define_attributes(object)
82
+ @attribute_definitions.each do |attribute_args|
83
+ object.attribute(*attribute_args)
84
+ end
85
+ end
86
+
87
+ end # module ModuleExtensions
88
+ end # module Virtus