omg-activemodel 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +67 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +99 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +55 -0
  8. data/lib/active_model/attribute.rb +277 -0
  9. data/lib/active_model/attribute_assignment.rb +78 -0
  10. data/lib/active_model/attribute_methods.rb +592 -0
  11. data/lib/active_model/attribute_mutation_tracker.rb +189 -0
  12. data/lib/active_model/attribute_registration.rb +117 -0
  13. data/lib/active_model/attribute_set/builder.rb +182 -0
  14. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  15. data/lib/active_model/attribute_set.rb +118 -0
  16. data/lib/active_model/attributes.rb +165 -0
  17. data/lib/active_model/callbacks.rb +155 -0
  18. data/lib/active_model/conversion.rb +121 -0
  19. data/lib/active_model/deprecator.rb +7 -0
  20. data/lib/active_model/dirty.rb +416 -0
  21. data/lib/active_model/error.rb +208 -0
  22. data/lib/active_model/errors.rb +547 -0
  23. data/lib/active_model/forbidden_attributes_protection.rb +33 -0
  24. data/lib/active_model/gem_version.rb +17 -0
  25. data/lib/active_model/lint.rb +118 -0
  26. data/lib/active_model/locale/en.yml +38 -0
  27. data/lib/active_model/model.rb +78 -0
  28. data/lib/active_model/naming.rb +359 -0
  29. data/lib/active_model/nested_error.rb +22 -0
  30. data/lib/active_model/railtie.rb +24 -0
  31. data/lib/active_model/secure_password.rb +231 -0
  32. data/lib/active_model/serialization.rb +198 -0
  33. data/lib/active_model/serializers/json.rb +154 -0
  34. data/lib/active_model/translation.rb +78 -0
  35. data/lib/active_model/type/big_integer.rb +36 -0
  36. data/lib/active_model/type/binary.rb +62 -0
  37. data/lib/active_model/type/boolean.rb +48 -0
  38. data/lib/active_model/type/date.rb +78 -0
  39. data/lib/active_model/type/date_time.rb +88 -0
  40. data/lib/active_model/type/decimal.rb +107 -0
  41. data/lib/active_model/type/float.rb +64 -0
  42. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
  43. data/lib/active_model/type/helpers/mutable.rb +24 -0
  44. data/lib/active_model/type/helpers/numeric.rb +61 -0
  45. data/lib/active_model/type/helpers/time_value.rb +127 -0
  46. data/lib/active_model/type/helpers/timezone.rb +23 -0
  47. data/lib/active_model/type/helpers.rb +7 -0
  48. data/lib/active_model/type/immutable_string.rb +71 -0
  49. data/lib/active_model/type/integer.rb +113 -0
  50. data/lib/active_model/type/registry.rb +37 -0
  51. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  52. data/lib/active_model/type/string.rb +43 -0
  53. data/lib/active_model/type/time.rb +87 -0
  54. data/lib/active_model/type/value.rb +157 -0
  55. data/lib/active_model/type.rb +55 -0
  56. data/lib/active_model/validations/absence.rb +33 -0
  57. data/lib/active_model/validations/acceptance.rb +113 -0
  58. data/lib/active_model/validations/callbacks.rb +119 -0
  59. data/lib/active_model/validations/clusivity.rb +54 -0
  60. data/lib/active_model/validations/comparability.rb +18 -0
  61. data/lib/active_model/validations/comparison.rb +90 -0
  62. data/lib/active_model/validations/confirmation.rb +80 -0
  63. data/lib/active_model/validations/exclusion.rb +49 -0
  64. data/lib/active_model/validations/format.rb +112 -0
  65. data/lib/active_model/validations/helper_methods.rb +15 -0
  66. data/lib/active_model/validations/inclusion.rb +47 -0
  67. data/lib/active_model/validations/length.rb +130 -0
  68. data/lib/active_model/validations/numericality.rb +222 -0
  69. data/lib/active_model/validations/presence.rb +39 -0
  70. data/lib/active_model/validations/resolve_value.rb +26 -0
  71. data/lib/active_model/validations/validates.rb +175 -0
  72. data/lib/active_model/validations/with.rb +154 -0
  73. data/lib/active_model/validations.rb +489 -0
  74. data/lib/active_model/validator.rb +190 -0
  75. data/lib/active_model/version.rb +10 -0
  76. data/lib/active_model.rb +84 -0
  77. metadata +139 -0
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/duplicable"
4
+
5
+ module ActiveModel
6
+ class Attribute # :nodoc:
7
+ class << self
8
+ def from_database(name, value_before_type_cast, type, value = nil)
9
+ FromDatabase.new(name, value_before_type_cast, type, nil, value)
10
+ end
11
+
12
+ def from_user(name, value_before_type_cast, type, original_attribute = nil)
13
+ FromUser.new(name, value_before_type_cast, type, original_attribute)
14
+ end
15
+
16
+ def with_cast_value(name, value_before_type_cast, type)
17
+ WithCastValue.new(name, value_before_type_cast, type)
18
+ end
19
+
20
+ def null(name)
21
+ Null.new(name)
22
+ end
23
+
24
+ def uninitialized(name, type)
25
+ Uninitialized.new(name, type)
26
+ end
27
+ end
28
+
29
+ attr_reader :name, :value_before_type_cast, :type
30
+
31
+ # This method should not be called directly.
32
+ # Use #from_database or #from_user
33
+ def initialize(name, value_before_type_cast, type, original_attribute = nil, value = nil)
34
+ @name = name
35
+ @value_before_type_cast = value_before_type_cast
36
+ @type = type
37
+ @original_attribute = original_attribute
38
+ @value = value unless value.nil?
39
+ end
40
+
41
+ def value
42
+ # `defined?` is cheaper than `||=` when we get back falsy values
43
+ @value = type_cast(value_before_type_cast) unless defined?(@value)
44
+ @value
45
+ end
46
+
47
+ def original_value
48
+ if assigned?
49
+ original_attribute.original_value
50
+ else
51
+ type_cast(value_before_type_cast)
52
+ end
53
+ end
54
+
55
+ def value_for_database
56
+ if !defined?(@value_for_database) || type.changed_in_place?(@value_for_database, value)
57
+ @value_for_database = _value_for_database
58
+ end
59
+ @value_for_database
60
+ end
61
+
62
+ def serializable?(&block)
63
+ type.serializable?(value, &block)
64
+ end
65
+
66
+ def changed?
67
+ changed_from_assignment? || changed_in_place?
68
+ end
69
+
70
+ def changed_in_place?
71
+ has_been_read? && type.changed_in_place?(original_value_for_database, value)
72
+ end
73
+
74
+ def forgetting_assignment
75
+ with_value_from_database(value_for_database)
76
+ end
77
+
78
+ def with_value_from_user(value)
79
+ type.assert_valid_value(value)
80
+ self.class.from_user(name, value, type, original_attribute || self)
81
+ end
82
+
83
+ def with_value_from_database(value)
84
+ self.class.from_database(name, value, type)
85
+ end
86
+
87
+ def with_cast_value(value)
88
+ self.class.with_cast_value(name, value, type)
89
+ end
90
+
91
+ def with_type(type)
92
+ if changed_in_place?
93
+ with_value_from_user(value).with_type(type)
94
+ else
95
+ self.class.new(name, value_before_type_cast, type, original_attribute)
96
+ end
97
+ end
98
+
99
+ def type_cast(*)
100
+ raise NotImplementedError
101
+ end
102
+
103
+ def initialized?
104
+ true
105
+ end
106
+
107
+ def came_from_user?
108
+ false
109
+ end
110
+
111
+ def has_been_read?
112
+ defined?(@value)
113
+ end
114
+
115
+ def ==(other)
116
+ self.class == other.class &&
117
+ name == other.name &&
118
+ value_before_type_cast == other.value_before_type_cast &&
119
+ type == other.type
120
+ end
121
+ alias eql? ==
122
+
123
+ def hash
124
+ [self.class, name, value_before_type_cast, type].hash
125
+ end
126
+
127
+ def init_with(coder)
128
+ @name = coder["name"]
129
+ @value_before_type_cast = coder["value_before_type_cast"]
130
+ @type = coder["type"]
131
+ @original_attribute = coder["original_attribute"]
132
+ @value = coder["value"] if coder.map.key?("value")
133
+ end
134
+
135
+ def encode_with(coder)
136
+ coder["name"] = name
137
+ coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
138
+ coder["type"] = type if type
139
+ coder["original_attribute"] = original_attribute if original_attribute
140
+ coder["value"] = value if defined?(@value)
141
+ end
142
+
143
+ def original_value_for_database
144
+ if assigned?
145
+ original_attribute.original_value_for_database
146
+ else
147
+ _original_value_for_database
148
+ end
149
+ end
150
+
151
+ private
152
+ attr_reader :original_attribute
153
+ alias :assigned? :original_attribute
154
+
155
+ def initialize_dup(other)
156
+ if @value&.duplicable?
157
+ @value = @value.dup
158
+ end
159
+ end
160
+
161
+ def changed_from_assignment?
162
+ assigned? && type.changed?(original_value, value, value_before_type_cast)
163
+ end
164
+
165
+ def _value_for_database
166
+ type.serialize(value)
167
+ end
168
+
169
+ def _original_value_for_database
170
+ type.serialize(original_value)
171
+ end
172
+
173
+ class FromDatabase < Attribute # :nodoc:
174
+ def type_cast(value)
175
+ type.deserialize(value)
176
+ end
177
+
178
+ def forgetting_assignment
179
+ # If this attribute was not persisted (with a `value_for_database`
180
+ # that might differ from `value_before_type_cast`) and `value` has not
181
+ # changed in place, we can use the existing `value_before_type_cast`
182
+ # to avoid deserialize / cast / serialize calls from computing the new
183
+ # attribute's `value_before_type_cast`.
184
+ if !defined?(@value_for_database) && !changed_in_place?
185
+ with_value_from_database(value_before_type_cast)
186
+ else
187
+ super
188
+ end
189
+ end
190
+
191
+ private
192
+ def _original_value_for_database
193
+ value_before_type_cast
194
+ end
195
+ end
196
+
197
+ class FromUser < Attribute # :nodoc:
198
+ def type_cast(value)
199
+ type.cast(value)
200
+ end
201
+
202
+ def came_from_user?
203
+ !type.value_constructed_by_mass_assignment?(value_before_type_cast)
204
+ end
205
+
206
+ private
207
+ def _value_for_database
208
+ Type::SerializeCastValue.serialize(type, value)
209
+ end
210
+ end
211
+
212
+ class WithCastValue < Attribute # :nodoc:
213
+ def type_cast(value)
214
+ value
215
+ end
216
+
217
+ def changed_in_place?
218
+ false
219
+ end
220
+ end
221
+
222
+ class Null < Attribute # :nodoc:
223
+ def initialize(name)
224
+ super(name, nil, Type.default_value)
225
+ end
226
+
227
+ def type_cast(*)
228
+ nil
229
+ end
230
+
231
+ def with_type(type)
232
+ self.class.with_cast_value(name, nil, type)
233
+ end
234
+
235
+ def with_value_from_database(value)
236
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
237
+ end
238
+ alias_method :with_value_from_user, :with_value_from_database
239
+ alias_method :with_cast_value, :with_value_from_database
240
+ end
241
+
242
+ class Uninitialized < Attribute # :nodoc:
243
+ UNINITIALIZED_ORIGINAL_VALUE = Object.new
244
+
245
+ def initialize(name, type)
246
+ super(name, nil, type)
247
+ end
248
+
249
+ def value
250
+ if block_given?
251
+ yield name
252
+ end
253
+ end
254
+
255
+ def original_value
256
+ UNINITIALIZED_ORIGINAL_VALUE
257
+ end
258
+
259
+ def value_for_database
260
+ end
261
+
262
+ def initialized?
263
+ false
264
+ end
265
+
266
+ def forgetting_assignment
267
+ dup
268
+ end
269
+
270
+ def with_type(type)
271
+ self.class.new(name, type)
272
+ end
273
+ end
274
+
275
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
276
+ end
277
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ module ActiveModel
6
+ module AttributeAssignment
7
+ include ActiveModel::ForbiddenAttributesProtection
8
+
9
+ # Allows you to set all the attributes by passing in a hash of attributes with
10
+ # keys matching the attribute names.
11
+ #
12
+ # If the passed hash responds to <tt>permitted?</tt> method and the return value
13
+ # of this method is +false+ an ActiveModel::ForbiddenAttributesError
14
+ # exception is raised.
15
+ #
16
+ # class Cat
17
+ # include ActiveModel::AttributeAssignment
18
+ # attr_accessor :name, :status
19
+ # end
20
+ #
21
+ # cat = Cat.new
22
+ # cat.assign_attributes(name: "Gorby", status: "yawning")
23
+ # cat.name # => 'Gorby'
24
+ # cat.status # => 'yawning'
25
+ # cat.assign_attributes(status: "sleeping")
26
+ # cat.name # => 'Gorby'
27
+ # cat.status # => 'sleeping'
28
+ def assign_attributes(new_attributes)
29
+ unless new_attributes.respond_to?(:each_pair)
30
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
31
+ end
32
+ return if new_attributes.empty?
33
+
34
+ _assign_attributes(sanitize_for_mass_assignment(new_attributes))
35
+ end
36
+
37
+ alias attributes= assign_attributes
38
+
39
+ # Like `BasicObject#method_missing`, `#attribute_writer_missing` is invoked
40
+ # when `#assign_attributes` is passed an unknown attribute name.
41
+ #
42
+ # By default, `#attribute_writer_missing` raises an UnknownAttributeError.
43
+ #
44
+ # class Rectangle
45
+ # include ActiveModel::AttributeAssignment
46
+ #
47
+ # attr_accessor :length, :width
48
+ #
49
+ # def attribute_writer_missing(name, value)
50
+ # Rails.logger.warn "Tried to assign to unknown attribute #{name}"
51
+ # end
52
+ # end
53
+ #
54
+ # rectangle = Rectangle.new
55
+ # rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"
56
+ def attribute_writer_missing(name, value)
57
+ raise UnknownAttributeError.new(self, name)
58
+ end
59
+
60
+ private
61
+ def _assign_attributes(attributes)
62
+ attributes.each do |k, v|
63
+ _assign_attribute(k, v)
64
+ end
65
+ end
66
+
67
+ def _assign_attribute(k, v)
68
+ setter = :"#{k}="
69
+ public_send(setter, v)
70
+ rescue NoMethodError
71
+ if respond_to?(setter)
72
+ raise
73
+ else
74
+ attribute_writer_missing(k.to_s, v)
75
+ end
76
+ end
77
+ end
78
+ end