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,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