activemodel 5.2.7.1 → 6.1.4.6

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -111
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -4
  5. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  6. data/lib/active_model/attribute.rb +21 -21
  7. data/lib/active_model/attribute_assignment.rb +4 -6
  8. data/lib/active_model/attribute_methods.rb +117 -40
  9. data/lib/active_model/attribute_mutation_tracker.rb +90 -33
  10. data/lib/active_model/attribute_set/builder.rb +81 -16
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  12. data/lib/active_model/attribute_set.rb +20 -28
  13. data/lib/active_model/attributes.rb +65 -44
  14. data/lib/active_model/callbacks.rb +11 -9
  15. data/lib/active_model/conversion.rb +1 -1
  16. data/lib/active_model/dirty.rb +51 -101
  17. data/lib/active_model/error.rb +207 -0
  18. data/lib/active_model/errors.rb +347 -155
  19. data/lib/active_model/gem_version.rb +4 -4
  20. data/lib/active_model/lint.rb +1 -1
  21. data/lib/active_model/naming.rb +22 -7
  22. data/lib/active_model/nested_error.rb +22 -0
  23. data/lib/active_model/railtie.rb +6 -0
  24. data/lib/active_model/secure_password.rb +54 -55
  25. data/lib/active_model/serialization.rb +9 -7
  26. data/lib/active_model/serializers/json.rb +17 -9
  27. data/lib/active_model/translation.rb +1 -1
  28. data/lib/active_model/type/big_integer.rb +0 -1
  29. data/lib/active_model/type/binary.rb +1 -1
  30. data/lib/active_model/type/boolean.rb +0 -1
  31. data/lib/active_model/type/date.rb +0 -5
  32. data/lib/active_model/type/date_time.rb +3 -8
  33. data/lib/active_model/type/decimal.rb +0 -1
  34. data/lib/active_model/type/float.rb +2 -3
  35. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
  36. data/lib/active_model/type/helpers/numeric.rb +17 -6
  37. data/lib/active_model/type/helpers/time_value.rb +37 -15
  38. data/lib/active_model/type/helpers/timezone.rb +1 -1
  39. data/lib/active_model/type/immutable_string.rb +14 -11
  40. data/lib/active_model/type/integer.rb +15 -18
  41. data/lib/active_model/type/registry.rb +16 -16
  42. data/lib/active_model/type/string.rb +12 -3
  43. data/lib/active_model/type/time.rb +1 -6
  44. data/lib/active_model/type/value.rb +9 -2
  45. data/lib/active_model/validations/absence.rb +2 -2
  46. data/lib/active_model/validations/acceptance.rb +34 -27
  47. data/lib/active_model/validations/callbacks.rb +15 -16
  48. data/lib/active_model/validations/clusivity.rb +6 -3
  49. data/lib/active_model/validations/confirmation.rb +4 -4
  50. data/lib/active_model/validations/exclusion.rb +1 -1
  51. data/lib/active_model/validations/format.rb +2 -3
  52. data/lib/active_model/validations/inclusion.rb +2 -2
  53. data/lib/active_model/validations/length.rb +3 -3
  54. data/lib/active_model/validations/numericality.rb +58 -44
  55. data/lib/active_model/validations/presence.rb +1 -1
  56. data/lib/active_model/validations/validates.rb +7 -6
  57. data/lib/active_model/validations.rb +6 -9
  58. data/lib/active_model/validator.rb +8 -3
  59. data/lib/active_model.rb +2 -1
  60. metadata +14 -9
@@ -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
- attribute_method_matchers.each do |matcher|
211
- matcher_new = matcher.method_name(new_name).to_s
212
- matcher_old = matcher.method_name(old_name).to_s
213
- define_proxy_call false, self, matcher_new, matcher_old
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
- attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
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
- attribute_method_matchers.each do |matcher|
286
- method_name = matcher.method_name(attr_name)
287
-
288
- unless instance_method_already_implemented?(method_name)
289
- generate_method = "define_method_#{matcher.method_missing_target}"
290
-
291
- if respond_to?(generate_method, true)
292
- send(generate_method, attr_name.to_s)
293
- else
294
- define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
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
- instance_methods.each { |m| undef_method(m) }
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
- # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
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, mod, name, send, *extra)
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(", ".freeze)
415
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
373
416
 
374
- target = if CALL_COMPILABLE_REGEXP.match?(send)
375
- "#{"self." unless include_private}#{send}(#{extra})"
417
+ body = if CALL_COMPILABLE_REGEXP.match?(target)
418
+ "#{"self." unless include_private}#{target}(#{extra})"
376
419
  else
377
- "send(:'#{send}', #{extra})"
420
+ "send(:'#{target}', #{extra})"
378
421
  end
379
422
 
380
- mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
381
- #{defn}
382
- #{target}
383
- end
384
- RUBY
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, :method_missing_target
431
+ attr_reader :prefix, :suffix, :target
389
432
 
390
- AttributeMethodMatch = Struct.new(:target, :attr_name, :method_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
- @method_missing_target = "#{@prefix}attribute#{@suffix}"
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(method_missing_target, $1, method_name)
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] = attributes[attr_name].original_value
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
- [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
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 = attr_name.to_s
48
- forced_changes.include?(attr_name) ||
49
- attributes[attr_name].changed? &&
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.to_s].changed_in_place?
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.to_s].original_value
60
+ attributes[attr_name].original_value
66
61
  end
67
62
 
68
63
  def force_change(attr_name)
69
- forced_changes << attr_name.to_s
64
+ forced_changes[attr_name] = fetch_value(attr_name)
70
65
  end
71
66
 
72
- # TODO Change this to private once we've dropped Ruby 2.2 support.
73
- # Workaround for Ruby 2.2 "private attribute?" warning.
74
- protected
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
- attr_reader :attributes, :forced_changes
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
- attributes.keys
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 forget_change(*)
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
- attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
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, :each_key, :each_value, :fetch, :except, to: :materialize
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 select
129
+ def each_key(&block)
61
130
  keys = types.keys | values.keys | delegate_hash.keys
62
- keys.each_with_object({}) do |key, hash|
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 6.2.
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])
@@ -33,8 +33,7 @@ module ActiveModel
33
33
  end
34
34
  end
35
35
 
36
- protected
37
-
36
+ private
38
37
  attr_reader :default_types
39
38
  end
40
39
  end