activemodel 5.2.8.1 → 6.1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -97
  3. data/MIT-LICENSE +1 -2
  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 +3 -3
  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 +55 -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 +17 -19
  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/type.rb +3 -2
  46. data/lib/active_model/validations/absence.rb +2 -2
  47. data/lib/active_model/validations/acceptance.rb +34 -27
  48. data/lib/active_model/validations/callbacks.rb +15 -16
  49. data/lib/active_model/validations/clusivity.rb +6 -3
  50. data/lib/active_model/validations/confirmation.rb +4 -4
  51. data/lib/active_model/validations/exclusion.rb +1 -1
  52. data/lib/active_model/validations/format.rb +2 -3
  53. data/lib/active_model/validations/inclusion.rb +2 -2
  54. data/lib/active_model/validations/length.rb +3 -3
  55. data/lib/active_model/validations/numericality.rb +58 -44
  56. data/lib/active_model/validations/presence.rb +1 -1
  57. data/lib/active_model/validations/validates.rb +7 -6
  58. data/lib/active_model/validations.rb +6 -9
  59. data/lib/active_model/validator.rb +8 -3
  60. data/lib/active_model.rb +2 -1
  61. 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
- 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 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])
@@ -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