activemodel 6.1.4.1 → 7.0.0.rc2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -66
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_model/api.rb +99 -0
  6. data/lib/active_model/attribute.rb +4 -0
  7. data/lib/active_model/attribute_methods.rb +65 -81
  8. data/lib/active_model/attribute_set/builder.rb +1 -10
  9. data/lib/active_model/attribute_set.rb +4 -1
  10. data/lib/active_model/attributes.rb +15 -12
  11. data/lib/active_model/callbacks.rb +1 -1
  12. data/lib/active_model/conversion.rb +2 -2
  13. data/lib/active_model/dirty.rb +6 -5
  14. data/lib/active_model/errors.rb +35 -235
  15. data/lib/active_model/gem_version.rb +4 -4
  16. data/lib/active_model/locale/en.yml +1 -0
  17. data/lib/active_model/model.rb +6 -59
  18. data/lib/active_model/naming.rb +15 -8
  19. data/lib/active_model/secure_password.rb +2 -1
  20. data/lib/active_model/serialization.rb +7 -2
  21. data/lib/active_model/translation.rb +1 -1
  22. data/lib/active_model/type/date.rb +1 -1
  23. data/lib/active_model/type/helpers/numeric.rb +9 -1
  24. data/lib/active_model/type/helpers/time_value.rb +3 -3
  25. data/lib/active_model/type/integer.rb +4 -1
  26. data/lib/active_model/type/registry.rb +9 -41
  27. data/lib/active_model/type/time.rb +1 -1
  28. data/lib/active_model/type.rb +6 -5
  29. data/lib/active_model/validations/absence.rb +1 -1
  30. data/lib/active_model/validations/clusivity.rb +1 -1
  31. data/lib/active_model/validations/comparability.rb +29 -0
  32. data/lib/active_model/validations/comparison.rb +82 -0
  33. data/lib/active_model/validations/confirmation.rb +4 -4
  34. data/lib/active_model/validations/numericality.rb +28 -21
  35. data/lib/active_model/validations.rb +4 -4
  36. data/lib/active_model/validator.rb +2 -2
  37. data/lib/active_model.rb +2 -1
  38. metadata +14 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62a6b320265b9ce1fe4dd2857f6cf0075d4d7c5bce68abb1141886e051742794
4
- data.tar.gz: 2f289a20c36b6aa3de45bbbb7dafb2a954571960b2737b3846f388d34f407778
3
+ metadata.gz: 9079e5f5b6e97cb434d3d881df63a462deaa5d76f4a903404a80a6fb352a4a34
4
+ data.tar.gz: 324766119f9caa91770f1948700d4085aad8a9814a2265d0a19a12a65ba040ec
5
5
  SHA512:
6
- metadata.gz: 187066c2525c7a9b8d79194171296ec0a6c1e7094d9773a682b53807b92b024a53fc9814fff4470bdf6443519b969855bff0477a8b2f9bc246a24fbfe2beb8a6
7
- data.tar.gz: 3edb340b99eb9a61623c38fe7d924c8629f4b477ef52c708b383debda325ad7130a9243b4e078d61a48864d70ecad095ad3265e8fbc1e6314db82e6d97a7c889
6
+ metadata.gz: 00dd34e112e676941a1c7a25d615dba688803f35dcd2c1fd71fc094490e26081d978728acb309d5cf588be6bcfaa22502017a2f058d33d83a09a35ac7f09d475
7
+ data.tar.gz: 24b905c955a57deb86c34ba76527f5f9ba7bc1b262825613db904b3d5abf189e463d131219217bbff3ce5ff452b60642886df6c50c8e4f11a3149cf3ebcac5df
data/CHANGELOG.md CHANGED
@@ -1,117 +1,128 @@
1
- ## Rails 6.1.4.1 (August 19, 2021) ##
1
+ ## Rails 7.0.0.rc2 (December 14, 2021) ##
2
2
 
3
- * No changes.
3
+ * Remove support to Marshal load Rails 5.x `ActiveModel::AttributeSet` format.
4
4
 
5
+ *Rafael Mendonça França*
5
6
 
6
- ## Rails 6.1.4 (June 24, 2021) ##
7
+ * Remove support to Marshal and YAML load Rails 5.x error format.
7
8
 
8
- * Fix `to_json` for `ActiveModel::Dirty` object.
9
+ *Rafael Mendonça França*
9
10
 
10
- Exclude +mutations_from_database+ attribute from json as it lead to recursion.
11
+ * Remove deprecated support to use `[]=` in `ActiveModel::Errors#messages`.
11
12
 
12
- *Anil Maurya*
13
+ *Rafael Mendonça França*
13
14
 
15
+ * Remove deprecated support to `delete` errors from `ActiveModel::Errors#messages`.
14
16
 
15
- ## Rails 6.1.3.2 (May 05, 2021) ##
17
+ *Rafael Mendonça França*
16
18
 
17
- * No changes.
19
+ * Remove deprecated support to `clear` errors from `ActiveModel::Errors#messages`.
18
20
 
21
+ *Rafael Mendonça França*
19
22
 
20
- ## Rails 6.1.3.1 (March 26, 2021) ##
23
+ * Remove deprecated support concat errors to `ActiveModel::Errors#messages`.
21
24
 
22
- * No changes.
25
+ *Rafael Mendonça França*
23
26
 
27
+ * Remove deprecated `ActiveModel::Errors#to_xml`.
24
28
 
25
- ## Rails 6.1.3 (February 17, 2021) ##
29
+ *Rafael Mendonça França*
26
30
 
27
- * No changes.
31
+ * Remove deprecated `ActiveModel::Errors#keys`.
28
32
 
33
+ *Rafael Mendonça França*
29
34
 
30
- ## Rails 6.1.2.1 (February 10, 2021) ##
35
+ * Remove deprecated `ActiveModel::Errors#values`.
31
36
 
32
- * No changes.
37
+ *Rafael Mendonça França*
33
38
 
39
+ * Remove deprecated `ActiveModel::Errors#slice!`.
34
40
 
35
- ## Rails 6.1.2 (February 09, 2021) ##
41
+ *Rafael Mendonça França*
36
42
 
37
- * No changes.
43
+ * Remove deprecated `ActiveModel::Errors#to_h`.
44
+
45
+ *Rafael Mendonça França*
46
+
47
+ * Remove deprecated enumeration of `ActiveModel::Errors` instances as a Hash.
48
+
49
+ *Rafael Mendonça França*
50
+
51
+ * Clear secure password cache if password is set to `nil`
38
52
 
53
+ Before:
39
54
 
40
- ## Rails 6.1.1 (January 07, 2021) ##
55
+ user.password = 'something'
56
+ user.password = nil
57
+
58
+ user.password # => 'something'
59
+
60
+ Now:
61
+
62
+ user.password = 'something'
63
+ user.password = nil
64
+
65
+ user.password # => nil
66
+
67
+ *Markus Doits*
68
+
69
+ ## Rails 7.0.0.alpha2 (September 15, 2021) ##
41
70
 
42
71
  * No changes.
43
72
 
44
73
 
45
- ## Rails 6.1.0 (December 09, 2020) ##
74
+ ## Rails 7.0.0.alpha1 (September 15, 2021) ##
46
75
 
47
- * Pass in `base` instead of `base_class` to Error.human_attribute_name
76
+ * Introduce `ActiveModel::API`.
48
77
 
49
- This is useful in cases where the `human_attribute_name` method depends
50
- on other attributes' values of the class under validation to derive what the
51
- attribute name should be.
78
+ Make `ActiveModel::API` the minimum API to talk with Action Pack and Action View.
79
+ This will allow adding more functionality to `ActiveModel::Model`.
52
80
 
53
- *Filipe Sabella*
81
+ *Petrik de Heus*, *Nathaniel Watts*
54
82
 
55
- * Deprecate marshalling load from legacy attributes format.
83
+ * Fix dirty check for Float::NaN and BigDecimal::NaN.
56
84
 
57
- *Ryuta Kamizono*
85
+ Float::NaN and BigDecimal::NaN in Ruby are [special values](https://bugs.ruby-lang.org/issues/1720)
86
+ and can't be compared with `==`.
58
87
 
59
- * `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
88
+ *Marcelo Lauxen*
60
89
 
61
- topic.update!(status: :archived)
62
- topic.status_previously_changed?(from: "active", to: "archived")
63
- # => true
90
+ * Fix `to_json` for `ActiveModel::Dirty` object.
64
91
 
65
- *George Claghorn*
92
+ Exclude `mutations_from_database` attribute from json as it lead to recursion.
66
93
 
67
- * Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
94
+ *Anil Maurya*
68
95
 
69
- class Animal
70
- include ActiveModel::Attributes
71
- attribute :age
72
- end
96
+ * Add `ActiveModel::AttributeSet#values_for_database`.
73
97
 
74
- animal = Animal.new
75
- animal.freeze
76
- animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
98
+ Returns attributes with values for assignment to the database.
77
99
 
78
- *Josh Brody*
100
+ *Chris Salzberg*
79
101
 
80
- * Add `*_previously_was` attribute methods when dirty tracking. Example:
102
+ * Fix delegation in ActiveModel::Type::Registry#lookup and ActiveModel::Type.lookup.
81
103
 
82
- pirate.update(catchphrase: "Ahoy!")
83
- pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"]
84
- pirate.catchphrase_previously_was # => "Thar She Blows!"
104
+ Passing a last positional argument `{}` would be incorrectly considered as keyword argument.
85
105
 
86
- *DHH*
106
+ *Benoit Daloze*
87
107
 
88
- * Encapsulate each validation error as an Error object.
108
+ * Cache and re-use generated attribute methods.
89
109
 
90
- The `ActiveModel`’s `errors` collection is now an array of these Error
91
- objects, instead of messages/details hash.
110
+ Generated methods with identical implementations will now share their instruction sequences
111
+ leading to reduced memory retention, and slightly faster load time.
92
112
 
93
- For each of these `Error` object, its `message` and `full_message` methods
94
- are for generating error messages. Its `details` method would return error’s
95
- extra parameters, found in the original `details` hash.
113
+ *Jean Boussier*
96
114
 
97
- The change tries its best at maintaining backward compatibility, however
98
- some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating
99
- `errors.messages` and `errors.details` hashes directly will have no effect. Moving forward,
100
- please convert those direct manipulations to use provided API methods instead.
115
+ * Add `in: range` parameter to `numericality` validator.
101
116
 
102
- The list of deprecated methods and their planned future behavioral changes at the next major release are:
117
+ *Michal Papis*
103
118
 
104
- * `errors#slice!` will be removed.
105
- * `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object.
106
- * `errors#values` will be removed.
107
- * `errors#keys` will be removed.
108
- * `errors#to_xml` will be removed.
109
- * `errors#to_h` will be removed, and can be replaced with `errors#to_hash`.
110
- * Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`).
111
- * Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect.
112
- * Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect.
119
+ * Add `locale` argument to `ActiveModel::Name#initialize` to be used to generate the `singular`,
120
+ `plural`, `route_key` and `singular_route_key` values.
113
121
 
114
- *lulalala*
122
+ *Lukas Pokorny*
115
123
 
124
+ * Make ActiveModel::Errors#inspect slimmer for readability
125
+
126
+ *lulalala*
116
127
 
117
- Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md) for previous changes.
128
+ Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2020 David Heinemeier Hansson
1
+ Copyright (c) 2004-2021 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -16,10 +16,10 @@ Model solves this by defining an explicit API. You can read more about the
16
16
  API in <tt>ActiveModel::Lint::Tests</tt>.
17
17
 
18
18
  Active Model provides a default module that implements the basic API required
19
- to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
19
+ to integrate with Action Pack out of the box: <tt>ActiveModel::API</tt>.
20
20
 
21
21
  class Person
22
- include ActiveModel::Model
22
+ include ActiveModel::API
23
23
 
24
24
  attr_accessor :name, :age
25
25
  validates_presence_of :name
@@ -32,7 +32,7 @@ to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
32
32
 
33
33
  It includes model name introspections, conversions, translations and
34
34
  validations, resulting in a class suitable to be used with Action Pack.
35
- See <tt>ActiveModel::Model</tt> for more examples.
35
+ See <tt>ActiveModel::API</tt> for more examples.
36
36
 
37
37
  Active Model also provides the following functionality to have ORM-like
38
38
  behavior out of the box:
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # == Active \Model \API
5
+ #
6
+ # Includes the required interface for an object to interact with
7
+ # Action Pack and Action View, using different Active Model modules.
8
+ # It includes model name introspections, conversions, translations and
9
+ # validations. Besides that, it allows you to initialize the object with a
10
+ # hash of attributes, pretty much like Active Record does.
11
+ #
12
+ # A minimal implementation could be:
13
+ #
14
+ # class Person
15
+ # include ActiveModel::API
16
+ # attr_accessor :name, :age
17
+ # end
18
+ #
19
+ # person = Person.new(name: 'bob', age: '18')
20
+ # person.name # => "bob"
21
+ # person.age # => "18"
22
+ #
23
+ # Note that, by default, <tt>ActiveModel::API</tt> implements <tt>persisted?</tt>
24
+ # to return +false+, which is the most common case. You may want to override
25
+ # it in your class to simulate a different scenario:
26
+ #
27
+ # class Person
28
+ # include ActiveModel::API
29
+ # attr_accessor :id, :name
30
+ #
31
+ # def persisted?
32
+ # self.id.present?
33
+ # end
34
+ # end
35
+ #
36
+ # person = Person.new(id: 1, name: 'bob')
37
+ # person.persisted? # => true
38
+ #
39
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make
40
+ # sure you call +super+ if you want the attributes hash initialization to
41
+ # happen.
42
+ #
43
+ # class Person
44
+ # include ActiveModel::API
45
+ # attr_accessor :id, :name, :omg
46
+ #
47
+ # def initialize(attributes={})
48
+ # super
49
+ # @omg ||= true
50
+ # end
51
+ # end
52
+ #
53
+ # person = Person.new(id: 1, name: 'bob')
54
+ # person.omg # => true
55
+ #
56
+ # For more detailed information on other functionalities available, please
57
+ # refer to the specific modules included in <tt>ActiveModel::API</tt>
58
+ # (see below).
59
+ module API
60
+ extend ActiveSupport::Concern
61
+ include ActiveModel::AttributeAssignment
62
+ include ActiveModel::Validations
63
+ include ActiveModel::Conversion
64
+
65
+ included do
66
+ extend ActiveModel::Naming
67
+ extend ActiveModel::Translation
68
+ end
69
+
70
+ # Initializes a new model with the given +params+.
71
+ #
72
+ # class Person
73
+ # include ActiveModel::API
74
+ # attr_accessor :name, :age
75
+ # end
76
+ #
77
+ # person = Person.new(name: 'bob', age: '18')
78
+ # person.name # => "bob"
79
+ # person.age # => "18"
80
+ def initialize(attributes = {})
81
+ assign_attributes(attributes) if attributes
82
+
83
+ super()
84
+ end
85
+
86
+ # Indicates if the model is persisted. Default is +false+.
87
+ #
88
+ # class Person
89
+ # include ActiveModel::API
90
+ # attr_accessor :id, :name
91
+ # end
92
+ #
93
+ # person = Person.new(id: 1, name: 'bob')
94
+ # person.persisted? # => false
95
+ def persisted?
96
+ false
97
+ end
98
+ end
99
+ end
@@ -56,6 +56,10 @@ module ActiveModel
56
56
  type.serialize(value)
57
57
  end
58
58
 
59
+ def serializable?(&block)
60
+ type.serializable?(value, &block)
61
+ end
62
+
59
63
  def changed?
60
64
  changed_from_assignment? || changed_in_place?
61
65
  end
@@ -67,6 +67,7 @@ module ActiveModel
67
67
 
68
68
  NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
69
69
  CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
70
+ FORWARD_PARAMETERS = "*args"
70
71
 
71
72
  included do
72
73
  class_attribute :attribute_aliases, instance_writer: false, default: {}
@@ -105,8 +106,8 @@ module ActiveModel
105
106
  # person.name # => "Bob"
106
107
  # person.clear_name
107
108
  # person.name # => nil
108
- def attribute_method_prefix(*prefixes)
109
- self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
109
+ def attribute_method_prefix(*prefixes, parameters: nil)
110
+ self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new(prefix: prefix, parameters: parameters) }
110
111
  undefine_attribute_methods
111
112
  end
112
113
 
@@ -140,8 +141,8 @@ module ActiveModel
140
141
  # person.name = 'Bob'
141
142
  # person.name # => "Bob"
142
143
  # person.name_short? # => true
143
- def attribute_method_suffix(*suffixes)
144
- self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
144
+ def attribute_method_suffix(*suffixes, parameters: nil)
145
+ self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new(suffix: suffix, parameters: parameters) }
145
146
  undefine_attribute_methods
146
147
  end
147
148
 
@@ -177,7 +178,7 @@ module ActiveModel
177
178
  # person.reset_name_to_default!
178
179
  # person.name # => 'Default Name'
179
180
  def attribute_method_affix(*affixes)
180
- self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
181
+ self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new(**affix) }
181
182
  undefine_attribute_methods
182
183
  end
183
184
 
@@ -207,11 +208,33 @@ module ActiveModel
207
208
  # person.nickname_short? # => true
208
209
  def alias_attribute(new_name, old_name)
209
210
  self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
210
- CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
211
+ ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
211
212
  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
213
+ method_name = matcher.method_name(new_name).to_s
214
+ target_name = matcher.method_name(old_name).to_s
215
+ parameters = matcher.parameters
216
+
217
+ mangled_name = target_name
218
+ unless NAME_COMPILABLE_REGEXP.match?(target_name)
219
+ mangled_name = "__temp__#{target_name.unpack1("h*")}"
220
+ end
221
+
222
+ code_generator.define_cached_method(method_name, as: mangled_name, namespace: :alias_attribute) do |batch|
223
+ body = if CALL_COMPILABLE_REGEXP.match?(target_name)
224
+ "self.#{target_name}(#{parameters || ''})"
225
+ else
226
+ call_args = [":'#{target_name}'"]
227
+ call_args << parameters if parameters
228
+ "send(#{call_args.join(", ")})"
229
+ end
230
+
231
+ modifier = matcher.parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
232
+
233
+ batch <<
234
+ "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
235
+ body <<
236
+ "end"
237
+ end
215
238
  end
216
239
  end
217
240
  end
@@ -251,7 +274,7 @@ module ActiveModel
251
274
  # end
252
275
  # end
253
276
  def define_attribute_methods(*attr_names)
254
- CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
277
+ ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
255
278
  attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
256
279
  end
257
280
  end
@@ -286,7 +309,7 @@ module ActiveModel
286
309
  # person.name # => "Bob"
287
310
  # person.name_short? # => true
288
311
  def define_attribute_method(attr_name, _owner: generated_attribute_methods)
289
- CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
312
+ ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
290
313
  attribute_method_matchers.each do |matcher|
291
314
  method_name = matcher.method_name(attr_name)
292
315
 
@@ -296,7 +319,7 @@ module ActiveModel
296
319
  if respond_to?(generate_method, true)
297
320
  send(generate_method, attr_name.to_s, owner: owner)
298
321
  else
299
- define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s
322
+ define_proxy_call(owner, method_name, matcher.target, matcher.parameters, attr_name.to_s, namespace: :active_model)
300
323
  end
301
324
  end
302
325
  end
@@ -335,46 +358,6 @@ module ActiveModel
335
358
  end
336
359
 
337
360
  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
-
378
361
  def generated_attribute_methods
379
362
  @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
380
363
  end
@@ -398,42 +381,48 @@ module ActiveModel
398
381
 
399
382
  def attribute_method_matchers_matching(method_name)
400
383
  attribute_method_matchers_cache.compute_if_absent(method_name) do
401
- attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
384
+ attribute_method_matchers.filter_map { |matcher| matcher.match(method_name) }
402
385
  end
403
386
  end
404
387
 
405
388
  # Define a method `name` in `mod` that dispatches to `send`
406
- # using the given `extra` args. This falls back on `define_method`
407
- # and `send` if the given names cannot be compiled.
408
- def define_proxy_call(include_private, code_generator, name, target, *extra)
409
- defn = if NAME_COMPILABLE_REGEXP.match?(name)
410
- "def #{name}(*args)"
411
- else
412
- "define_method(:'#{name}') do |*args|"
389
+ # using the given `extra` args. This falls back on `send`
390
+ # if the called name cannot be compiled.
391
+ def define_proxy_call(code_generator, name, target, parameters, *call_args, namespace:)
392
+ mangled_name = name
393
+ unless NAME_COMPILABLE_REGEXP.match?(name)
394
+ mangled_name = "__temp__#{name.unpack1("h*")}"
413
395
  end
414
396
 
415
- extra = (extra.map!(&:inspect) << "*args").join(", ")
397
+ code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
398
+ call_args.map!(&:inspect)
399
+ call_args << parameters if parameters
416
400
 
417
- body = if CALL_COMPILABLE_REGEXP.match?(target)
418
- "#{"self." unless include_private}#{target}(#{extra})"
419
- else
420
- "send(:'#{target}', #{extra})"
421
- end
401
+ body = if CALL_COMPILABLE_REGEXP.match?(target)
402
+ "self.#{target}(#{call_args.join(", ")})"
403
+ else
404
+ call_args.unshift(":'#{target}'")
405
+ "send(#{call_args.join(", ")})"
406
+ end
407
+
408
+ modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
422
409
 
423
- code_generator <<
424
- defn <<
425
- body <<
426
- "end" <<
427
- "ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
410
+ batch <<
411
+ "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
412
+ body <<
413
+ "end"
414
+ end
428
415
  end
429
416
 
430
- class AttributeMethodMatcher #:nodoc:
431
- attr_reader :prefix, :suffix, :target
417
+ class AttributeMethodMatcher # :nodoc:
418
+ attr_reader :prefix, :suffix, :target, :parameters
432
419
 
433
420
  AttributeMethodMatch = Struct.new(:target, :attr_name)
434
421
 
435
- def initialize(options = {})
436
- @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
422
+ def initialize(prefix: "", suffix: "", parameters: nil)
423
+ @prefix = prefix
424
+ @suffix = suffix
425
+ @parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
437
426
  @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
438
427
  @target = "#{@prefix}attribute#{@suffix}"
439
428
  @method_name = "#{prefix}%s#{suffix}"
@@ -469,7 +458,7 @@ module ActiveModel
469
458
  match ? attribute_missing(match, *args, &block) : super
470
459
  end
471
460
  end
472
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
461
+ ruby2_keywords(:method_missing)
473
462
 
474
463
  # +attribute_missing+ is like +method_missing+, but for attributes. When
475
464
  # +method_missing+ is called we check to see if there is a matching
@@ -520,10 +509,6 @@ module ActiveModel
520
509
 
521
510
  # We want to generate the methods via module_eval rather than
522
511
  # 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
512
  #
528
513
  # But sometimes the database might return columns with
529
514
  # characters that are not allowed in normal method names (like
@@ -547,7 +532,6 @@ module ActiveModel
547
532
  temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
548
533
  attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
549
534
  yield temp_method_name, attr_name_expr
550
- owner.rename_method(temp_method_name, method_name)
551
535
  end
552
536
  end
553
537
  end
@@ -144,16 +144,7 @@ module ActiveModel
144
144
  end
145
145
 
146
146
  def marshal_load(values)
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
151
- empty_hash = {}.freeze
152
- initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
153
- @materialized = true
154
- else
155
- initialize(*values)
156
- end
147
+ initialize(*values)
157
148
  end
158
149
 
159
150
  protected
@@ -25,6 +25,10 @@ module ActiveModel
25
25
  attributes.transform_values(&:value_before_type_cast)
26
26
  end
27
27
 
28
+ def values_for_database
29
+ attributes.transform_values(&:value_for_database)
30
+ end
31
+
28
32
  def to_hash
29
33
  keys.index_with { |name| self[name].value }
30
34
  end
@@ -54,7 +58,6 @@ module ActiveModel
54
58
 
55
59
  def write_cast_value(name, value)
56
60
  @attributes[name] = self[name].with_cast_value(value)
57
- value
58
61
  end
59
62
 
60
63
  def freeze