activemodel 7.0.4 → 7.1.3.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +162 -107
- data/MIT-LICENSE +1 -1
- data/README.rdoc +11 -11
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +5 -5
- data/lib/active_model/attribute/user_provided_default.rb +4 -0
- data/lib/active_model/attribute.rb +26 -1
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +133 -102
- data/lib/active_model/attribute_mutation_tracker.rb +10 -2
- data/lib/active_model/attribute_registration.rb +77 -0
- data/lib/active_model/attribute_set.rb +10 -1
- data/lib/active_model/attributes.rb +62 -45
- data/lib/active_model/callbacks.rb +6 -6
- data/lib/active_model/conversion.rb +14 -4
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +134 -13
- data/lib/active_model/error.rb +5 -4
- data/lib/active_model/errors.rb +37 -6
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +34 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +61 -23
- data/lib/active_model/serialization.rb +3 -3
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/translation.rb +18 -16
- data/lib/active_model/type/big_integer.rb +23 -1
- data/lib/active_model/type/binary.rb +13 -3
- data/lib/active_model/type/boolean.rb +11 -9
- data/lib/active_model/type/date.rb +28 -2
- data/lib/active_model/type/date_time.rb +45 -3
- data/lib/active_model/type/decimal.rb +39 -1
- data/lib/active_model/type/float.rb +30 -1
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
- data/lib/active_model/type/helpers/mutable.rb +4 -0
- data/lib/active_model/type/helpers/numeric.rb +6 -1
- data/lib/active_model/type/helpers/time_value.rb +28 -12
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +9 -1
- data/lib/active_model/type/time.rb +48 -7
- data/lib/active_model/type/value.rb +25 -1
- data/lib/active_model/type.rb +1 -0
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +10 -12
- data/lib/active_model/validations/clusivity.rb +5 -8
- data/lib/active_model/validations/comparability.rb +0 -11
- data/lib/active_model/validations/comparison.rb +15 -7
- data/lib/active_model/validations/format.rb +6 -7
- data/lib/active_model/validations/length.rb +13 -12
- data/lib/active_model/validations/numericality.rb +35 -23
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +5 -5
- data/lib/active_model/validations/with.rb +9 -2
- data/lib/active_model/validations.rb +44 -9
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +6 -1
- metadata +13 -8
|
@@ -11,17 +11,17 @@ module ActiveModel
|
|
|
11
11
|
#
|
|
12
12
|
# user = User.first
|
|
13
13
|
# user.pets.select(:id).first.user_id
|
|
14
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
|
14
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
|
|
15
15
|
class MissingAttributeError < NoMethodError
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
# = Active \Model \Attribute \Methods
|
|
19
19
|
#
|
|
20
20
|
# Provides a way to add prefixes and suffixes to your methods as
|
|
21
|
-
# well as handling the creation of
|
|
21
|
+
# well as handling the creation of ActiveRecord::Base - like
|
|
22
22
|
# class methods such as +table_name+.
|
|
23
23
|
#
|
|
24
|
-
# The requirements to implement
|
|
24
|
+
# The requirements to implement +ActiveModel::AttributeMethods+ are to:
|
|
25
25
|
#
|
|
26
26
|
# * <tt>include ActiveModel::AttributeMethods</tt> in your class.
|
|
27
27
|
# * Call each of its methods you want to add, such as +attribute_method_suffix+
|
|
@@ -49,18 +49,17 @@ module ActiveModel
|
|
|
49
49
|
# end
|
|
50
50
|
#
|
|
51
51
|
# private
|
|
52
|
+
# def attribute_contrived?(attr)
|
|
53
|
+
# true
|
|
54
|
+
# end
|
|
52
55
|
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# def clear_attribute(attr)
|
|
58
|
-
# send("#{attr}=", nil)
|
|
59
|
-
# end
|
|
56
|
+
# def clear_attribute(attr)
|
|
57
|
+
# send("#{attr}=", nil)
|
|
58
|
+
# end
|
|
60
59
|
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
60
|
+
# def reset_attribute_to_default!(attr)
|
|
61
|
+
# send("#{attr}=", 'Default Name')
|
|
62
|
+
# end
|
|
64
63
|
# end
|
|
65
64
|
module AttributeMethods
|
|
66
65
|
extend ActiveSupport::Concern
|
|
@@ -71,7 +70,7 @@ module ActiveModel
|
|
|
71
70
|
|
|
72
71
|
included do
|
|
73
72
|
class_attribute :attribute_aliases, instance_writer: false, default: {}
|
|
74
|
-
class_attribute :
|
|
73
|
+
class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ]
|
|
75
74
|
end
|
|
76
75
|
|
|
77
76
|
module ClassMethods
|
|
@@ -95,10 +94,9 @@ module ActiveModel
|
|
|
95
94
|
# define_attribute_methods :name
|
|
96
95
|
#
|
|
97
96
|
# private
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
# end
|
|
97
|
+
# def clear_attribute(attr)
|
|
98
|
+
# send("#{attr}=", nil)
|
|
99
|
+
# end
|
|
102
100
|
# end
|
|
103
101
|
#
|
|
104
102
|
# person = Person.new
|
|
@@ -107,7 +105,7 @@ module ActiveModel
|
|
|
107
105
|
# person.clear_name
|
|
108
106
|
# person.name # => nil
|
|
109
107
|
def attribute_method_prefix(*prefixes, parameters: nil)
|
|
110
|
-
self.
|
|
108
|
+
self.attribute_method_patterns += prefixes.map! { |prefix| AttributeMethodPattern.new(prefix: prefix, parameters: parameters) }
|
|
111
109
|
undefine_attribute_methods
|
|
112
110
|
end
|
|
113
111
|
|
|
@@ -131,10 +129,9 @@ module ActiveModel
|
|
|
131
129
|
# define_attribute_methods :name
|
|
132
130
|
#
|
|
133
131
|
# private
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
# end
|
|
132
|
+
# def attribute_short?(attr)
|
|
133
|
+
# send(attr).length < 5
|
|
134
|
+
# end
|
|
138
135
|
# end
|
|
139
136
|
#
|
|
140
137
|
# person = Person.new
|
|
@@ -142,7 +139,7 @@ module ActiveModel
|
|
|
142
139
|
# person.name # => "Bob"
|
|
143
140
|
# person.name_short? # => true
|
|
144
141
|
def attribute_method_suffix(*suffixes, parameters: nil)
|
|
145
|
-
self.
|
|
142
|
+
self.attribute_method_patterns += suffixes.map! { |suffix| AttributeMethodPattern.new(suffix: suffix, parameters: parameters) }
|
|
146
143
|
undefine_attribute_methods
|
|
147
144
|
end
|
|
148
145
|
|
|
@@ -167,10 +164,9 @@ module ActiveModel
|
|
|
167
164
|
# define_attribute_methods :name
|
|
168
165
|
#
|
|
169
166
|
# private
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
# end
|
|
167
|
+
# def reset_attribute_to_default!(attr)
|
|
168
|
+
# send("#{attr}=", 'Default Name')
|
|
169
|
+
# end
|
|
174
170
|
# end
|
|
175
171
|
#
|
|
176
172
|
# person = Person.new
|
|
@@ -178,7 +174,7 @@ module ActiveModel
|
|
|
178
174
|
# person.reset_name_to_default!
|
|
179
175
|
# person.name # => 'Default Name'
|
|
180
176
|
def attribute_method_affix(*affixes)
|
|
181
|
-
self.
|
|
177
|
+
self.attribute_method_patterns += affixes.map! { |affix| AttributeMethodPattern.new(**affix) }
|
|
182
178
|
undefine_attribute_methods
|
|
183
179
|
end
|
|
184
180
|
|
|
@@ -194,10 +190,9 @@ module ActiveModel
|
|
|
194
190
|
# alias_attribute :nickname, :name
|
|
195
191
|
#
|
|
196
192
|
# private
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
# end
|
|
193
|
+
# def attribute_short?(attr)
|
|
194
|
+
# send(attr).length < 5
|
|
195
|
+
# end
|
|
201
196
|
# end
|
|
202
197
|
#
|
|
203
198
|
# person = Person.new
|
|
@@ -207,35 +202,50 @@ module ActiveModel
|
|
|
207
202
|
# person.name_short? # => true
|
|
208
203
|
# person.nickname_short? # => true
|
|
209
204
|
def alias_attribute(new_name, old_name)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
mangled_name = target_name
|
|
218
|
-
unless NAME_COMPILABLE_REGEXP.match?(target_name)
|
|
219
|
-
mangled_name = "__temp__#{target_name.unpack1("h*")}"
|
|
220
|
-
end
|
|
205
|
+
old_name = old_name.to_s
|
|
206
|
+
new_name = new_name.to_s
|
|
207
|
+
self.attribute_aliases = attribute_aliases.merge(new_name => old_name)
|
|
208
|
+
aliases_by_attribute_name[old_name] << new_name
|
|
209
|
+
eagerly_generate_alias_attribute_methods(new_name, old_name)
|
|
210
|
+
end
|
|
221
211
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
call_args << parameters if parameters
|
|
228
|
-
"send(#{call_args.join(", ")})"
|
|
229
|
-
end
|
|
212
|
+
def eagerly_generate_alias_attribute_methods(new_name, old_name) # :nodoc:
|
|
213
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
|
214
|
+
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
230
217
|
|
|
231
|
-
|
|
218
|
+
def generate_alias_attribute_methods(code_generator, new_name, old_name)
|
|
219
|
+
attribute_method_patterns.each do |pattern|
|
|
220
|
+
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
232
223
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
224
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
|
|
225
|
+
method_name = pattern.method_name(new_name).to_s
|
|
226
|
+
target_name = pattern.method_name(old_name).to_s
|
|
227
|
+
parameters = pattern.parameters
|
|
228
|
+
mangled_name = target_name
|
|
229
|
+
|
|
230
|
+
unless NAME_COMPILABLE_REGEXP.match?(target_name)
|
|
231
|
+
mangled_name = "__temp__#{target_name.unpack1("h*")}"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
code_generator.define_cached_method(method_name, as: mangled_name, namespace: :alias_attribute) do |batch|
|
|
235
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target_name)
|
|
236
|
+
"self.#{target_name}(#{parameters || ''})"
|
|
237
|
+
else
|
|
238
|
+
call_args = [":'#{target_name}'"]
|
|
239
|
+
call_args << parameters if parameters
|
|
240
|
+
"send(#{call_args.join(", ")})"
|
|
238
241
|
end
|
|
242
|
+
|
|
243
|
+
modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
|
|
244
|
+
|
|
245
|
+
batch <<
|
|
246
|
+
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
|
247
|
+
body <<
|
|
248
|
+
"end"
|
|
239
249
|
end
|
|
240
250
|
end
|
|
241
251
|
|
|
@@ -250,7 +260,7 @@ module ActiveModel
|
|
|
250
260
|
end
|
|
251
261
|
|
|
252
262
|
# Declares the attributes that should be prefixed and suffixed by
|
|
253
|
-
#
|
|
263
|
+
# +ActiveModel::AttributeMethods+.
|
|
254
264
|
#
|
|
255
265
|
# To use, pass attribute names (as strings or symbols). Be sure to declare
|
|
256
266
|
# +define_attribute_methods+ after you define any prefix, suffix, or affix
|
|
@@ -268,19 +278,23 @@ module ActiveModel
|
|
|
268
278
|
# define_attribute_methods :name, :age, :address
|
|
269
279
|
#
|
|
270
280
|
# private
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
#
|
|
274
|
-
# end
|
|
281
|
+
# def clear_attribute(attr)
|
|
282
|
+
# send("#{attr}=", nil)
|
|
283
|
+
# end
|
|
275
284
|
# end
|
|
276
285
|
def define_attribute_methods(*attr_names)
|
|
277
286
|
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
|
278
|
-
attr_names.flatten.each
|
|
287
|
+
attr_names.flatten.each do |attr_name|
|
|
288
|
+
define_attribute_method(attr_name, _owner: owner)
|
|
289
|
+
aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
|
|
290
|
+
generate_alias_attribute_methods owner, aliased_name, attr_name
|
|
291
|
+
end
|
|
292
|
+
end
|
|
279
293
|
end
|
|
280
294
|
end
|
|
281
295
|
|
|
282
296
|
# Declares an attribute that should be prefixed and suffixed by
|
|
283
|
-
#
|
|
297
|
+
# +ActiveModel::AttributeMethods+.
|
|
284
298
|
#
|
|
285
299
|
# To use, pass an attribute name (as string or symbol). Be sure to declare
|
|
286
300
|
# +define_attribute_method+ after you define any prefix, suffix or affix
|
|
@@ -298,10 +312,9 @@ module ActiveModel
|
|
|
298
312
|
# define_attribute_method :name
|
|
299
313
|
#
|
|
300
314
|
# private
|
|
301
|
-
#
|
|
302
|
-
#
|
|
303
|
-
#
|
|
304
|
-
# end
|
|
315
|
+
# def attribute_short?(attr)
|
|
316
|
+
# send(attr).length < 5
|
|
317
|
+
# end
|
|
305
318
|
# end
|
|
306
319
|
#
|
|
307
320
|
# person = Person.new
|
|
@@ -310,24 +323,24 @@ module ActiveModel
|
|
|
310
323
|
# person.name_short? # => true
|
|
311
324
|
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
|
|
312
325
|
ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
|
313
|
-
|
|
314
|
-
method_name =
|
|
326
|
+
attribute_method_patterns.each do |pattern|
|
|
327
|
+
method_name = pattern.method_name(attr_name)
|
|
315
328
|
|
|
316
329
|
unless instance_method_already_implemented?(method_name)
|
|
317
|
-
generate_method = "define_method_#{
|
|
330
|
+
generate_method = "define_method_#{pattern.proxy_target}"
|
|
318
331
|
|
|
319
332
|
if respond_to?(generate_method, true)
|
|
320
333
|
send(generate_method, attr_name.to_s, owner: owner)
|
|
321
334
|
else
|
|
322
|
-
define_proxy_call(owner, method_name,
|
|
335
|
+
define_proxy_call(owner, method_name, pattern.proxy_target, pattern.parameters, attr_name.to_s, namespace: :active_model_proxy)
|
|
323
336
|
end
|
|
324
337
|
end
|
|
325
338
|
end
|
|
326
|
-
|
|
339
|
+
attribute_method_patterns_cache.clear
|
|
327
340
|
end
|
|
328
341
|
end
|
|
329
342
|
|
|
330
|
-
# Removes all the previously dynamically defined methods from the class.
|
|
343
|
+
# Removes all the previously dynamically defined methods from the class, including alias attribute methods.
|
|
331
344
|
#
|
|
332
345
|
# class Person
|
|
333
346
|
# include ActiveModel::AttributeMethods
|
|
@@ -335,29 +348,46 @@ module ActiveModel
|
|
|
335
348
|
# attr_accessor :name
|
|
336
349
|
# attribute_method_suffix '_short?'
|
|
337
350
|
# define_attribute_method :name
|
|
351
|
+
# alias_attribute :first_name, :name
|
|
338
352
|
#
|
|
339
353
|
# private
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
#
|
|
343
|
-
# end
|
|
354
|
+
# def attribute_short?(attr)
|
|
355
|
+
# send(attr).length < 5
|
|
356
|
+
# end
|
|
344
357
|
# end
|
|
345
358
|
#
|
|
346
359
|
# person = Person.new
|
|
347
360
|
# person.name = 'Bob'
|
|
361
|
+
# person.first_name # => "Bob"
|
|
348
362
|
# person.name_short? # => true
|
|
349
363
|
#
|
|
350
364
|
# Person.undefine_attribute_methods
|
|
351
365
|
#
|
|
352
366
|
# person.name_short? # => NoMethodError
|
|
367
|
+
# person.first_name # => NoMethodError
|
|
353
368
|
def undefine_attribute_methods
|
|
354
369
|
generated_attribute_methods.module_eval do
|
|
355
370
|
undef_method(*instance_methods)
|
|
356
371
|
end
|
|
357
|
-
|
|
372
|
+
attribute_method_patterns_cache.clear
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def aliases_by_attribute_name # :nodoc:
|
|
376
|
+
@aliases_by_attribute_name ||= Hash.new { |h, k| h[k] = [] }
|
|
358
377
|
end
|
|
359
378
|
|
|
360
379
|
private
|
|
380
|
+
def inherited(base) # :nodoc:
|
|
381
|
+
super
|
|
382
|
+
base.class_eval do
|
|
383
|
+
@attribute_method_patterns_cache = nil
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def resolve_attribute_name(name)
|
|
388
|
+
attribute_aliases.fetch(super, &:itself)
|
|
389
|
+
end
|
|
390
|
+
|
|
361
391
|
def generated_attribute_methods
|
|
362
392
|
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
|
363
393
|
end
|
|
@@ -375,33 +405,34 @@ module ActiveModel
|
|
|
375
405
|
# used to alleviate the GC, which ultimately also speeds up the app
|
|
376
406
|
# significantly (in our case our test suite finishes 10% faster with
|
|
377
407
|
# this cache).
|
|
378
|
-
def
|
|
379
|
-
@
|
|
408
|
+
def attribute_method_patterns_cache
|
|
409
|
+
@attribute_method_patterns_cache ||= Concurrent::Map.new(initial_capacity: 4)
|
|
380
410
|
end
|
|
381
411
|
|
|
382
|
-
def
|
|
383
|
-
|
|
384
|
-
|
|
412
|
+
def attribute_method_patterns_matching(method_name)
|
|
413
|
+
attribute_method_patterns_cache.compute_if_absent(method_name) do
|
|
414
|
+
attribute_method_patterns.filter_map { |pattern| pattern.match(method_name) }
|
|
385
415
|
end
|
|
386
416
|
end
|
|
387
417
|
|
|
388
418
|
# Define a method `name` in `mod` that dispatches to `send`
|
|
389
419
|
# using the given `extra` args. This falls back on `send`
|
|
390
420
|
# if the called name cannot be compiled.
|
|
391
|
-
def define_proxy_call(code_generator, name,
|
|
421
|
+
def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:)
|
|
392
422
|
mangled_name = name
|
|
393
423
|
unless NAME_COMPILABLE_REGEXP.match?(name)
|
|
394
424
|
mangled_name = "__temp__#{name.unpack1("h*")}"
|
|
395
425
|
end
|
|
396
426
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
427
|
+
call_args.map!(&:inspect)
|
|
428
|
+
call_args << parameters if parameters
|
|
429
|
+
namespace = :"#{namespace}_#{proxy_target}_#{call_args.join("_")}}"
|
|
400
430
|
|
|
401
|
-
|
|
402
|
-
|
|
431
|
+
code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
|
|
432
|
+
body = if CALL_COMPILABLE_REGEXP.match?(proxy_target)
|
|
433
|
+
"self.#{proxy_target}(#{call_args.join(", ")})"
|
|
403
434
|
else
|
|
404
|
-
call_args.unshift(":'#{
|
|
435
|
+
call_args.unshift(":'#{proxy_target}'")
|
|
405
436
|
"send(#{call_args.join(", ")})"
|
|
406
437
|
end
|
|
407
438
|
|
|
@@ -414,23 +445,23 @@ module ActiveModel
|
|
|
414
445
|
end
|
|
415
446
|
end
|
|
416
447
|
|
|
417
|
-
class
|
|
418
|
-
attr_reader :prefix, :suffix, :
|
|
448
|
+
class AttributeMethodPattern # :nodoc:
|
|
449
|
+
attr_reader :prefix, :suffix, :proxy_target, :parameters
|
|
419
450
|
|
|
420
|
-
|
|
451
|
+
AttributeMethod = Struct.new(:proxy_target, :attr_name)
|
|
421
452
|
|
|
422
453
|
def initialize(prefix: "", suffix: "", parameters: nil)
|
|
423
454
|
@prefix = prefix
|
|
424
455
|
@suffix = suffix
|
|
425
456
|
@parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
|
|
426
|
-
@regex =
|
|
427
|
-
@
|
|
457
|
+
@regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
|
|
458
|
+
@proxy_target = "#{@prefix}attribute#{@suffix}"
|
|
428
459
|
@method_name = "#{prefix}%s#{suffix}"
|
|
429
460
|
end
|
|
430
461
|
|
|
431
462
|
def match(method_name)
|
|
432
463
|
if @regex =~ method_name
|
|
433
|
-
|
|
464
|
+
AttributeMethod.new(proxy_target, $1)
|
|
434
465
|
end
|
|
435
466
|
end
|
|
436
467
|
|
|
@@ -465,7 +496,7 @@ module ActiveModel
|
|
|
465
496
|
# attribute method. If so, we tell +attribute_missing+ to dispatch the
|
|
466
497
|
# attribute. This method can be overloaded to customize the behavior.
|
|
467
498
|
def attribute_missing(match, *args, &block)
|
|
468
|
-
__send__(match.
|
|
499
|
+
__send__(match.proxy_target, match.attr_name, *args, &block)
|
|
469
500
|
end
|
|
470
501
|
ruby2_keywords(:attribute_missing)
|
|
471
502
|
|
|
@@ -493,12 +524,12 @@ module ActiveModel
|
|
|
493
524
|
# Returns a struct representing the matching attribute method.
|
|
494
525
|
# The struct's attributes are prefix, base and suffix.
|
|
495
526
|
def matched_attribute_method(method_name)
|
|
496
|
-
matches = self.class.send(:
|
|
527
|
+
matches = self.class.send(:attribute_method_patterns_matching, method_name)
|
|
497
528
|
matches.detect { |match| attribute_method?(match.attr_name) }
|
|
498
529
|
end
|
|
499
530
|
|
|
500
531
|
def missing_attribute(attr_name, stack)
|
|
501
|
-
raise ActiveModel::MissingAttributeError, "missing attribute
|
|
532
|
+
raise ActiveModel::MissingAttributeError, "missing attribute '#{attr_name}' for #{self.class}", stack
|
|
502
533
|
end
|
|
503
534
|
|
|
504
535
|
def _read_attribute(attr)
|
|
@@ -43,8 +43,8 @@ module ActiveModel
|
|
|
43
43
|
|
|
44
44
|
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
|
45
45
|
attribute_changed?(attr_name) &&
|
|
46
|
-
(OPTION_NOT_GIVEN == from || original_value(attr_name) == from) &&
|
|
47
|
-
(OPTION_NOT_GIVEN == to || fetch_value(attr_name) == to)
|
|
46
|
+
(OPTION_NOT_GIVEN == from || original_value(attr_name) == type_cast(attr_name, from)) &&
|
|
47
|
+
(OPTION_NOT_GIVEN == to || fetch_value(attr_name) == type_cast(attr_name, to))
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def changed_in_place?(attr_name)
|
|
@@ -82,6 +82,10 @@ module ActiveModel
|
|
|
82
82
|
def fetch_value(attr_name)
|
|
83
83
|
attributes.fetch_value(attr_name)
|
|
84
84
|
end
|
|
85
|
+
|
|
86
|
+
def type_cast(attr_name, value)
|
|
87
|
+
attributes[attr_name].type_cast(value)
|
|
88
|
+
end
|
|
85
89
|
end
|
|
86
90
|
|
|
87
91
|
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
|
@@ -143,6 +147,10 @@ module ActiveModel
|
|
|
143
147
|
rescue TypeError, NoMethodError
|
|
144
148
|
value
|
|
145
149
|
end
|
|
150
|
+
|
|
151
|
+
def type_cast(attr_name, value)
|
|
152
|
+
value
|
|
153
|
+
end
|
|
146
154
|
end
|
|
147
155
|
|
|
148
156
|
class NullMutationTracker # :nodoc:
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/class/subclasses"
|
|
4
|
+
require "active_model/attribute_set"
|
|
5
|
+
require "active_model/attribute/user_provided_default"
|
|
6
|
+
|
|
7
|
+
module ActiveModel
|
|
8
|
+
module AttributeRegistration # :nodoc:
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
module ClassMethods # :nodoc:
|
|
12
|
+
def attribute(name, type = nil, default: (no_default = true), **options)
|
|
13
|
+
type = resolve_type_name(type, **options) if type.is_a?(Symbol)
|
|
14
|
+
|
|
15
|
+
pending = pending_attribute(name)
|
|
16
|
+
pending.type = type if type
|
|
17
|
+
pending.default = default unless no_default
|
|
18
|
+
|
|
19
|
+
reset_default_attributes
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def _default_attributes # :nodoc:
|
|
23
|
+
@default_attributes ||= build_default_attributes
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def attribute_types # :nodoc:
|
|
27
|
+
@attribute_types ||= _default_attributes.cast_types.tap do |hash|
|
|
28
|
+
hash.default = Type.default_value
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
class PendingAttribute # :nodoc:
|
|
34
|
+
attr_accessor :type, :default
|
|
35
|
+
|
|
36
|
+
def apply_to(attribute)
|
|
37
|
+
attribute = attribute.with_type(type || attribute.type)
|
|
38
|
+
attribute = attribute.with_user_default(default) if defined?(@default)
|
|
39
|
+
attribute
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def pending_attribute(name)
|
|
44
|
+
@pending_attributes ||= {}
|
|
45
|
+
@pending_attributes[resolve_attribute_name(name)] ||= PendingAttribute.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def apply_pending_attributes(attribute_set)
|
|
49
|
+
superclass.send(__method__, attribute_set) if superclass.respond_to?(__method__, true)
|
|
50
|
+
|
|
51
|
+
defined?(@pending_attributes) && @pending_attributes.each do |name, pending|
|
|
52
|
+
attribute_set[name] = pending.apply_to(attribute_set[name])
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
attribute_set
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_default_attributes
|
|
59
|
+
apply_pending_attributes(AttributeSet.new({}))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def reset_default_attributes
|
|
63
|
+
@default_attributes = nil
|
|
64
|
+
@attribute_types = nil
|
|
65
|
+
subclasses.each { |subclass| subclass.send(__method__) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def resolve_attribute_name(name)
|
|
69
|
+
name.to_s
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def resolve_type_name(name, **options)
|
|
73
|
+
Type.lookup(name, **options)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -21,6 +21,10 @@ module ActiveModel
|
|
|
21
21
|
@attributes[name] = value
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def cast_types
|
|
25
|
+
attributes.transform_values(&:type)
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def values_before_type_cast
|
|
25
29
|
attributes.transform_values(&:value_before_type_cast)
|
|
26
30
|
end
|
|
@@ -37,6 +41,7 @@ module ActiveModel
|
|
|
37
41
|
def key?(name)
|
|
38
42
|
attributes.key?(name) && self[name].initialized?
|
|
39
43
|
end
|
|
44
|
+
alias :include? :key?
|
|
40
45
|
|
|
41
46
|
def keys
|
|
42
47
|
attributes.each_key.select { |name| self[name].initialized? }
|
|
@@ -94,8 +99,12 @@ module ActiveModel
|
|
|
94
99
|
AttributeSet.new(new_attributes)
|
|
95
100
|
end
|
|
96
101
|
|
|
102
|
+
def reverse_merge!(target_attributes)
|
|
103
|
+
attributes.reverse_merge!(target_attributes.attributes) && self
|
|
104
|
+
end
|
|
105
|
+
|
|
97
106
|
def ==(other)
|
|
98
|
-
attributes == other.attributes
|
|
107
|
+
other.is_a?(AttributeSet) && attributes == other.send(:attributes)
|
|
99
108
|
end
|
|
100
109
|
|
|
101
110
|
protected
|