activemodel 5.2.8.1 → 6.1.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +91 -97
- data/MIT-LICENSE +1 -2
- data/README.rdoc +6 -4
- data/lib/active_model/attribute/user_provided_default.rb +1 -2
- data/lib/active_model/attribute.rb +21 -21
- data/lib/active_model/attribute_assignment.rb +4 -6
- data/lib/active_model/attribute_methods.rb +117 -40
- data/lib/active_model/attribute_mutation_tracker.rb +90 -33
- data/lib/active_model/attribute_set/builder.rb +81 -16
- data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
- data/lib/active_model/attribute_set.rb +20 -28
- data/lib/active_model/attributes.rb +65 -44
- data/lib/active_model/callbacks.rb +11 -9
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +51 -101
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +347 -155
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +22 -7
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +6 -0
- data/lib/active_model/secure_password.rb +55 -55
- data/lib/active_model/serialization.rb +9 -7
- data/lib/active_model/serializers/json.rb +17 -9
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/big_integer.rb +0 -1
- data/lib/active_model/type/binary.rb +1 -1
- data/lib/active_model/type/boolean.rb +0 -1
- data/lib/active_model/type/date.rb +0 -5
- data/lib/active_model/type/date_time.rb +3 -8
- data/lib/active_model/type/decimal.rb +0 -1
- data/lib/active_model/type/float.rb +2 -3
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
- data/lib/active_model/type/helpers/numeric.rb +17 -6
- data/lib/active_model/type/helpers/time_value.rb +37 -15
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -11
- data/lib/active_model/type/integer.rb +15 -18
- data/lib/active_model/type/registry.rb +17 -19
- data/lib/active_model/type/string.rb +12 -3
- data/lib/active_model/type/time.rb +1 -6
- data/lib/active_model/type/value.rb +9 -2
- data/lib/active_model/type.rb +3 -2
- data/lib/active_model/validations/absence.rb +2 -2
- data/lib/active_model/validations/acceptance.rb +34 -27
- data/lib/active_model/validations/callbacks.rb +15 -16
- data/lib/active_model/validations/clusivity.rb +6 -3
- data/lib/active_model/validations/confirmation.rb +4 -4
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -3
- data/lib/active_model/validations/inclusion.rb +2 -2
- data/lib/active_model/validations/length.rb +3 -3
- data/lib/active_model/validations/numericality.rb +58 -44
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +7 -6
- data/lib/active_model/validations.rb +6 -9
- data/lib/active_model/validator.rb +8 -3
- data/lib/active_model.rb +2 -1
- metadata +13 -7
@@ -207,10 +207,12 @@ module ActiveModel
|
|
207
207
|
# person.nickname_short? # => true
|
208
208
|
def alias_attribute(new_name, old_name)
|
209
209
|
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
210
|
+
CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
|
211
|
+
attribute_method_matchers.each do |matcher|
|
212
|
+
matcher_new = matcher.method_name(new_name).to_s
|
213
|
+
matcher_old = matcher.method_name(old_name).to_s
|
214
|
+
define_proxy_call false, owner, matcher_new, matcher_old
|
215
|
+
end
|
214
216
|
end
|
215
217
|
end
|
216
218
|
|
@@ -249,7 +251,9 @@ module ActiveModel
|
|
249
251
|
# end
|
250
252
|
# end
|
251
253
|
def define_attribute_methods(*attr_names)
|
252
|
-
|
254
|
+
CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
255
|
+
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
|
256
|
+
end
|
253
257
|
end
|
254
258
|
|
255
259
|
# Declares an attribute that should be prefixed and suffixed by
|
@@ -281,21 +285,23 @@ module ActiveModel
|
|
281
285
|
# person.name = 'Bob'
|
282
286
|
# person.name # => "Bob"
|
283
287
|
# person.name_short? # => true
|
284
|
-
def define_attribute_method(attr_name)
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
288
|
+
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
|
289
|
+
CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
290
|
+
attribute_method_matchers.each do |matcher|
|
291
|
+
method_name = matcher.method_name(attr_name)
|
292
|
+
|
293
|
+
unless instance_method_already_implemented?(method_name)
|
294
|
+
generate_method = "define_method_#{matcher.target}"
|
295
|
+
|
296
|
+
if respond_to?(generate_method, true)
|
297
|
+
send(generate_method, attr_name.to_s, owner: owner)
|
298
|
+
else
|
299
|
+
define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s
|
300
|
+
end
|
295
301
|
end
|
296
302
|
end
|
303
|
+
attribute_method_matchers_cache.clear
|
297
304
|
end
|
298
|
-
attribute_method_matchers_cache.clear
|
299
305
|
end
|
300
306
|
|
301
307
|
# Removes all the previously dynamically defined methods from the class.
|
@@ -323,12 +329,52 @@ module ActiveModel
|
|
323
329
|
# person.name_short? # => NoMethodError
|
324
330
|
def undefine_attribute_methods
|
325
331
|
generated_attribute_methods.module_eval do
|
326
|
-
|
332
|
+
undef_method(*instance_methods)
|
327
333
|
end
|
328
334
|
attribute_method_matchers_cache.clear
|
329
335
|
end
|
330
336
|
|
331
337
|
private
|
338
|
+
class CodeGenerator
|
339
|
+
class << self
|
340
|
+
def batch(owner, path, line)
|
341
|
+
if owner.is_a?(CodeGenerator)
|
342
|
+
yield owner
|
343
|
+
else
|
344
|
+
instance = new(owner, path, line)
|
345
|
+
result = yield instance
|
346
|
+
instance.execute
|
347
|
+
result
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def initialize(owner, path, line)
|
353
|
+
@owner = owner
|
354
|
+
@path = path
|
355
|
+
@line = line
|
356
|
+
@sources = ["# frozen_string_literal: true\n"]
|
357
|
+
@renames = {}
|
358
|
+
end
|
359
|
+
|
360
|
+
def <<(source_line)
|
361
|
+
@sources << source_line
|
362
|
+
end
|
363
|
+
|
364
|
+
def rename_method(old_name, new_name)
|
365
|
+
@renames[old_name] = new_name
|
366
|
+
end
|
367
|
+
|
368
|
+
def execute
|
369
|
+
@owner.module_eval(@sources.join(";"), @path, @line - 1)
|
370
|
+
@renames.each do |old_name, new_name|
|
371
|
+
@owner.alias_method new_name, old_name
|
372
|
+
@owner.undef_method old_name
|
373
|
+
end
|
374
|
+
end
|
375
|
+
end
|
376
|
+
private_constant :CodeGenerator
|
377
|
+
|
332
378
|
def generated_attribute_methods
|
333
379
|
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
334
380
|
end
|
@@ -352,63 +398,56 @@ module ActiveModel
|
|
352
398
|
|
353
399
|
def attribute_method_matchers_matching(method_name)
|
354
400
|
attribute_method_matchers_cache.compute_if_absent(method_name) do
|
355
|
-
|
356
|
-
# will match every time.
|
357
|
-
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
|
358
|
-
matchers.map { |method| method.match(method_name) }.compact
|
401
|
+
attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
|
359
402
|
end
|
360
403
|
end
|
361
404
|
|
362
405
|
# Define a method `name` in `mod` that dispatches to `send`
|
363
406
|
# using the given `extra` args. This falls back on `define_method`
|
364
407
|
# and `send` if the given names cannot be compiled.
|
365
|
-
def define_proxy_call(include_private,
|
408
|
+
def define_proxy_call(include_private, code_generator, name, target, *extra)
|
366
409
|
defn = if NAME_COMPILABLE_REGEXP.match?(name)
|
367
410
|
"def #{name}(*args)"
|
368
411
|
else
|
369
412
|
"define_method(:'#{name}') do |*args|"
|
370
413
|
end
|
371
414
|
|
372
|
-
extra = (extra.map!(&:inspect) << "*args").join(", "
|
415
|
+
extra = (extra.map!(&:inspect) << "*args").join(", ")
|
373
416
|
|
374
|
-
|
375
|
-
"#{"self." unless include_private}#{
|
417
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target)
|
418
|
+
"#{"self." unless include_private}#{target}(#{extra})"
|
376
419
|
else
|
377
|
-
"send(:'#{
|
420
|
+
"send(:'#{target}', #{extra})"
|
378
421
|
end
|
379
422
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
end
|
384
|
-
|
423
|
+
code_generator <<
|
424
|
+
defn <<
|
425
|
+
body <<
|
426
|
+
"end" <<
|
427
|
+
"ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
|
385
428
|
end
|
386
429
|
|
387
430
|
class AttributeMethodMatcher #:nodoc:
|
388
|
-
attr_reader :prefix, :suffix, :
|
431
|
+
attr_reader :prefix, :suffix, :target
|
389
432
|
|
390
|
-
AttributeMethodMatch = Struct.new(:target, :attr_name
|
433
|
+
AttributeMethodMatch = Struct.new(:target, :attr_name)
|
391
434
|
|
392
435
|
def initialize(options = {})
|
393
436
|
@prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
|
394
437
|
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
|
395
|
-
@
|
438
|
+
@target = "#{@prefix}attribute#{@suffix}"
|
396
439
|
@method_name = "#{prefix}%s#{suffix}"
|
397
440
|
end
|
398
441
|
|
399
442
|
def match(method_name)
|
400
443
|
if @regex =~ method_name
|
401
|
-
AttributeMethodMatch.new(
|
444
|
+
AttributeMethodMatch.new(target, $1)
|
402
445
|
end
|
403
446
|
end
|
404
447
|
|
405
448
|
def method_name(attr_name)
|
406
449
|
@method_name % attr_name
|
407
450
|
end
|
408
|
-
|
409
|
-
def plain?
|
410
|
-
prefix.empty? && suffix.empty?
|
411
|
-
end
|
412
451
|
end
|
413
452
|
end
|
414
453
|
|
@@ -430,6 +469,7 @@ module ActiveModel
|
|
430
469
|
match ? attribute_missing(match, *args, &block) : super
|
431
470
|
end
|
432
471
|
end
|
472
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
433
473
|
|
434
474
|
# +attribute_missing+ is like +method_missing+, but for attributes. When
|
435
475
|
# +method_missing+ is called we check to see if there is a matching
|
@@ -474,5 +514,42 @@ module ActiveModel
|
|
474
514
|
def _read_attribute(attr)
|
475
515
|
__send__(attr)
|
476
516
|
end
|
517
|
+
|
518
|
+
module AttrNames # :nodoc:
|
519
|
+
DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
|
520
|
+
|
521
|
+
# We want to generate the methods via module_eval rather than
|
522
|
+
# define_method, because define_method is slower on dispatch.
|
523
|
+
# Evaluating many similar methods may use more memory as the instruction
|
524
|
+
# sequences are duplicated and cached (in MRI). define_method may
|
525
|
+
# be slower on dispatch, but if you're careful about the closure
|
526
|
+
# created, then define_method will consume much less memory.
|
527
|
+
#
|
528
|
+
# But sometimes the database might return columns with
|
529
|
+
# characters that are not allowed in normal method names (like
|
530
|
+
# 'my_column(omg)'. So to work around this we first define with
|
531
|
+
# the __temp__ identifier, and then use alias method to rename
|
532
|
+
# it to what we want.
|
533
|
+
#
|
534
|
+
# We are also defining a constant to hold the frozen string of
|
535
|
+
# the attribute name. Using a constant means that we do not have
|
536
|
+
# to allocate an object on each call to the attribute method.
|
537
|
+
# Making it frozen means that it doesn't get duped when used to
|
538
|
+
# key the @attributes in read_attribute.
|
539
|
+
def self.define_attribute_accessor_method(owner, attr_name, writer: false)
|
540
|
+
method_name = "#{attr_name}#{'=' if writer}"
|
541
|
+
if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
|
542
|
+
yield method_name, "'#{attr_name}'"
|
543
|
+
else
|
544
|
+
safe_name = attr_name.unpack1("h*")
|
545
|
+
const_name = "ATTR_#{safe_name}"
|
546
|
+
const_set(const_name, attr_name) unless const_defined?(const_name)
|
547
|
+
temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
|
548
|
+
attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
|
549
|
+
yield temp_method_name, attr_name_expr
|
550
|
+
owner.rename_method(temp_method_name, method_name)
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
477
554
|
end
|
478
555
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
require "active_support/core_ext/object/duplicable"
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
class AttributeMutationTracker # :nodoc:
|
@@ -8,7 +9,6 @@ module ActiveModel
|
|
8
9
|
|
9
10
|
def initialize(attributes)
|
10
11
|
@attributes = attributes
|
11
|
-
@forced_changes = Set.new
|
12
12
|
end
|
13
13
|
|
14
14
|
def changed_attribute_names
|
@@ -18,24 +18,22 @@ module ActiveModel
|
|
18
18
|
def changed_values
|
19
19
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
20
20
|
if changed?(attr_name)
|
21
|
-
result[attr_name] =
|
21
|
+
result[attr_name] = original_value(attr_name)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
26
|
def changes
|
27
27
|
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
|
28
|
-
change = change_to_attribute(attr_name)
|
29
|
-
if change
|
28
|
+
if change = change_to_attribute(attr_name)
|
30
29
|
result.merge!(attr_name => change)
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
34
|
def change_to_attribute(attr_name)
|
36
|
-
attr_name = attr_name.to_s
|
37
35
|
if changed?(attr_name)
|
38
|
-
[
|
36
|
+
[original_value(attr_name), fetch_value(attr_name)]
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
@@ -44,81 +42,140 @@ module ActiveModel
|
|
44
42
|
end
|
45
43
|
|
46
44
|
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
47
|
-
attr_name
|
48
|
-
|
49
|
-
|
50
|
-
(OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
|
51
|
-
(OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
|
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)
|
52
48
|
end
|
53
49
|
|
54
50
|
def changed_in_place?(attr_name)
|
55
|
-
attributes[attr_name
|
51
|
+
attributes[attr_name].changed_in_place?
|
56
52
|
end
|
57
53
|
|
58
54
|
def forget_change(attr_name)
|
59
|
-
attr_name = attr_name.to_s
|
60
55
|
attributes[attr_name] = attributes[attr_name].forgetting_assignment
|
61
56
|
forced_changes.delete(attr_name)
|
62
57
|
end
|
63
58
|
|
64
59
|
def original_value(attr_name)
|
65
|
-
attributes[attr_name
|
60
|
+
attributes[attr_name].original_value
|
66
61
|
end
|
67
62
|
|
68
63
|
def force_change(attr_name)
|
69
|
-
forced_changes
|
64
|
+
forced_changes[attr_name] = fetch_value(attr_name)
|
70
65
|
end
|
71
66
|
|
72
|
-
|
73
|
-
|
74
|
-
|
67
|
+
private
|
68
|
+
attr_reader :attributes
|
69
|
+
|
70
|
+
def forced_changes
|
71
|
+
@forced_changes ||= {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def attr_names
|
75
|
+
attributes.keys
|
76
|
+
end
|
75
77
|
|
76
|
-
|
78
|
+
def attribute_changed?(attr_name)
|
79
|
+
forced_changes.include?(attr_name) || !!attributes[attr_name].changed?
|
80
|
+
end
|
81
|
+
|
82
|
+
def fetch_value(attr_name)
|
83
|
+
attributes.fetch_value(attr_name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
|
88
|
+
def initialize(attributes)
|
89
|
+
super
|
90
|
+
@finalized_changes = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def changed_in_place?(attr_name)
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def change_to_attribute(attr_name)
|
98
|
+
if finalized_changes&.include?(attr_name)
|
99
|
+
finalized_changes[attr_name].dup
|
100
|
+
else
|
101
|
+
super
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def forget_change(attr_name)
|
106
|
+
forced_changes.delete(attr_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def original_value(attr_name)
|
110
|
+
if changed?(attr_name)
|
111
|
+
forced_changes[attr_name]
|
112
|
+
else
|
113
|
+
fetch_value(attr_name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def force_change(attr_name)
|
118
|
+
forced_changes[attr_name] = clone_value(attr_name) unless attribute_changed?(attr_name)
|
119
|
+
end
|
120
|
+
|
121
|
+
def finalize_changes
|
122
|
+
@finalized_changes = changes
|
123
|
+
end
|
77
124
|
|
78
125
|
private
|
126
|
+
attr_reader :finalized_changes
|
79
127
|
|
80
128
|
def attr_names
|
81
|
-
|
129
|
+
forced_changes.keys
|
130
|
+
end
|
131
|
+
|
132
|
+
def attribute_changed?(attr_name)
|
133
|
+
forced_changes.include?(attr_name)
|
134
|
+
end
|
135
|
+
|
136
|
+
def fetch_value(attr_name)
|
137
|
+
attributes.send(:_read_attribute, attr_name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def clone_value(attr_name)
|
141
|
+
value = fetch_value(attr_name)
|
142
|
+
value.duplicable? ? value.clone : value
|
143
|
+
rescue TypeError, NoMethodError
|
144
|
+
value
|
82
145
|
end
|
83
146
|
end
|
84
147
|
|
85
148
|
class NullMutationTracker # :nodoc:
|
86
149
|
include Singleton
|
87
150
|
|
88
|
-
def changed_attribute_names
|
151
|
+
def changed_attribute_names
|
89
152
|
[]
|
90
153
|
end
|
91
154
|
|
92
|
-
def changed_values
|
155
|
+
def changed_values
|
93
156
|
{}
|
94
157
|
end
|
95
158
|
|
96
|
-
def changes
|
159
|
+
def changes
|
97
160
|
{}
|
98
161
|
end
|
99
162
|
|
100
163
|
def change_to_attribute(attr_name)
|
101
164
|
end
|
102
165
|
|
103
|
-
def any_changes?
|
166
|
+
def any_changes?
|
104
167
|
false
|
105
168
|
end
|
106
169
|
|
107
|
-
def changed?(
|
170
|
+
def changed?(attr_name, **)
|
108
171
|
false
|
109
172
|
end
|
110
173
|
|
111
|
-
def changed_in_place?(
|
174
|
+
def changed_in_place?(attr_name)
|
112
175
|
false
|
113
176
|
end
|
114
177
|
|
115
|
-
def
|
116
|
-
end
|
117
|
-
|
118
|
-
def original_value(*)
|
119
|
-
end
|
120
|
-
|
121
|
-
def force_change(*)
|
178
|
+
def original_value(attr_name)
|
122
179
|
end
|
123
180
|
end
|
124
181
|
end
|
@@ -13,14 +13,86 @@ module ActiveModel
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def build_from_database(values = {}, additional_types = {})
|
16
|
-
|
17
|
-
AttributeSet.new(attributes)
|
16
|
+
LazyAttributeSet.new(values, types, additional_types, default_attributes)
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
21
|
+
class LazyAttributeSet < AttributeSet # :nodoc:
|
22
|
+
def initialize(values, types, additional_types, default_attributes, attributes = {})
|
23
|
+
super(attributes)
|
24
|
+
@values = values
|
25
|
+
@types = types
|
26
|
+
@additional_types = additional_types
|
27
|
+
@default_attributes = default_attributes
|
28
|
+
@casted_values = {}
|
29
|
+
@materialized = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def key?(name)
|
33
|
+
(values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
|
34
|
+
end
|
35
|
+
|
36
|
+
def keys
|
37
|
+
keys = values.keys | types.keys | @attributes.keys
|
38
|
+
keys.keep_if { |name| self[name].initialized? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_value(name, &block)
|
42
|
+
if attr = @attributes[name]
|
43
|
+
return attr.value(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
@casted_values.fetch(name) do
|
47
|
+
value_present = true
|
48
|
+
value = values.fetch(name) { value_present = false }
|
49
|
+
|
50
|
+
if value_present
|
51
|
+
type = additional_types.fetch(name, types[name])
|
52
|
+
@casted_values[name] = type.deserialize(value)
|
53
|
+
else
|
54
|
+
attr = default_attribute(name, value_present, value)
|
55
|
+
attr.value(&block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
def attributes
|
62
|
+
unless @materialized
|
63
|
+
values.each_key { |key| self[key] }
|
64
|
+
types.each_key { |key| self[key] }
|
65
|
+
@materialized = true
|
66
|
+
end
|
67
|
+
@attributes
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
attr_reader :values, :types, :additional_types, :default_attributes
|
72
|
+
|
73
|
+
def default_attribute(
|
74
|
+
name,
|
75
|
+
value_present = true,
|
76
|
+
value = values.fetch(name) { value_present = false }
|
77
|
+
)
|
78
|
+
type = additional_types.fetch(name, types[name])
|
79
|
+
|
80
|
+
if value_present
|
81
|
+
@attributes[name] = Attribute.from_database(name, value, type, @casted_values[name])
|
82
|
+
elsif types.key?(name)
|
83
|
+
if attr = default_attributes[name]
|
84
|
+
@attributes[name] = attr.dup
|
85
|
+
else
|
86
|
+
@attributes[name] = Attribute.uninitialized(name, type)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
Attribute.null(name)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
22
94
|
class LazyAttributeHash # :nodoc:
|
23
|
-
delegate :transform_values, :
|
95
|
+
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
|
24
96
|
|
25
97
|
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
|
26
98
|
@types = types
|
@@ -40,9 +112,6 @@ module ActiveModel
|
|
40
112
|
end
|
41
113
|
|
42
114
|
def []=(key, value)
|
43
|
-
if frozen?
|
44
|
-
raise RuntimeError, "Can't modify frozen hash"
|
45
|
-
end
|
46
115
|
delegate_hash[key] = value
|
47
116
|
end
|
48
117
|
|
@@ -57,14 +126,9 @@ module ActiveModel
|
|
57
126
|
super
|
58
127
|
end
|
59
128
|
|
60
|
-
def
|
129
|
+
def each_key(&block)
|
61
130
|
keys = types.keys | values.keys | delegate_hash.keys
|
62
|
-
keys.
|
63
|
-
attribute = self[key]
|
64
|
-
if yield(key, attribute)
|
65
|
-
hash[key] = attribute
|
66
|
-
end
|
67
|
-
end
|
131
|
+
keys.each(&block)
|
68
132
|
end
|
69
133
|
|
70
134
|
def ==(other)
|
@@ -81,6 +145,9 @@ module ActiveModel
|
|
81
145
|
|
82
146
|
def marshal_load(values)
|
83
147
|
if values.is_a?(Hash)
|
148
|
+
ActiveSupport::Deprecation.warn(<<~MSG)
|
149
|
+
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 7.0.
|
150
|
+
MSG
|
84
151
|
empty_hash = {}.freeze
|
85
152
|
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
|
86
153
|
@materialized = true
|
@@ -90,9 +157,6 @@ module ActiveModel
|
|
90
157
|
end
|
91
158
|
|
92
159
|
protected
|
93
|
-
|
94
|
-
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
95
|
-
|
96
160
|
def materialize
|
97
161
|
unless @materialized
|
98
162
|
values.each_key { |key| self[key] }
|
@@ -105,6 +169,7 @@ module ActiveModel
|
|
105
169
|
end
|
106
170
|
|
107
171
|
private
|
172
|
+
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
|
108
173
|
|
109
174
|
def assign_default_value(name)
|
110
175
|
type = additional_types.fetch(name, types[name])
|