activemodel 7.0.4 → 6.1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -103
  3. data/MIT-LICENSE +0 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_model/attribute.rb +0 -4
  6. data/lib/active_model/attribute_methods.rb +82 -67
  7. data/lib/active_model/attribute_set/builder.rb +10 -1
  8. data/lib/active_model/attribute_set.rb +1 -4
  9. data/lib/active_model/attributes.rb +12 -15
  10. data/lib/active_model/callbacks.rb +3 -3
  11. data/lib/active_model/conversion.rb +2 -2
  12. data/lib/active_model/dirty.rb +4 -5
  13. data/lib/active_model/error.rb +2 -2
  14. data/lib/active_model/errors.rb +248 -55
  15. data/lib/active_model/gem_version.rb +5 -5
  16. data/lib/active_model/locale/en.yml +0 -1
  17. data/lib/active_model/model.rb +59 -6
  18. data/lib/active_model/naming.rb +8 -15
  19. data/lib/active_model/secure_password.rb +2 -25
  20. data/lib/active_model/serialization.rb +2 -6
  21. data/lib/active_model/translation.rb +2 -2
  22. data/lib/active_model/type/date.rb +1 -1
  23. data/lib/active_model/type/helpers/numeric.rb +1 -9
  24. data/lib/active_model/type/helpers/time_value.rb +3 -3
  25. data/lib/active_model/type/integer.rb +1 -4
  26. data/lib/active_model/type/registry.rb +38 -8
  27. data/lib/active_model/type/time.rb +1 -1
  28. data/lib/active_model/type.rb +6 -6
  29. data/lib/active_model/validations/absence.rb +2 -2
  30. data/lib/active_model/validations/acceptance.rb +1 -1
  31. data/lib/active_model/validations/callbacks.rb +1 -1
  32. data/lib/active_model/validations/clusivity.rb +1 -1
  33. data/lib/active_model/validations/confirmation.rb +5 -5
  34. data/lib/active_model/validations/exclusion.rb +3 -3
  35. data/lib/active_model/validations/format.rb +1 -1
  36. data/lib/active_model/validations/inclusion.rb +3 -3
  37. data/lib/active_model/validations/length.rb +2 -2
  38. data/lib/active_model/validations/numericality.rb +22 -29
  39. data/lib/active_model/validations/presence.rb +1 -1
  40. data/lib/active_model/validations/validates.rb +3 -3
  41. data/lib/active_model/validations/with.rb +4 -4
  42. data/lib/active_model/validations.rb +12 -12
  43. data/lib/active_model/validator.rb +5 -5
  44. data/lib/active_model/version.rb +1 -1
  45. data/lib/active_model.rb +0 -1
  46. metadata +9 -12
  47. data/lib/active_model/api.rb +0 -99
  48. data/lib/active_model/validations/comparability.rb +0 -29
  49. data/lib/active_model/validations/comparison.rb +0 -82
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 630260cc44166916794fb7e43002e72a303ff43b834e82e530cff1f13f25ee7f
4
- data.tar.gz: f136cec57987c03ab4aa3fba2b65fe2964b010a326e28cdc0d32f755b45612f1
3
+ metadata.gz: d2706dba9ce622452b7e50ce891f67af2eb27717b37ed0279148ad064ef1c6ce
4
+ data.tar.gz: 16856f088c941213ab4647ec5041995c8eaadf1ff230e76a98eb4ddd34ce0856
5
5
  SHA512:
6
- metadata.gz: 9b16a3dd6f96bf2ec1bfa5e3abba2e513567b12035e34e8ea30c24fb8a08ca70089d394f5daa0f40fdc4e14fb63470593904a5a335f864782983c719cf0d57d2
7
- data.tar.gz: 66f3f86fcf4b90ffbab8ea82777ac2df48f1774f7ff528d44ec81e12ea47cb2050309b5535e9e960757eb8f9350efa9c6a128754c3b117fdedef3e3c42a43765
6
+ metadata.gz: 8b234b3e5a18c9831af5d029ee72d91481c4a82f539f4184ca9d274c0d3d13529e8fa3e71ac34a2caaa614b74db98da21432914e2d04212a9232b61b6e585ee7
7
+ data.tar.gz: 10c2d8a7571fa6340749ccfef90e1f54b423a6f1c259adf6961c0043ccfea04e5400e7f76b733e8d0a0f4d81dfe3c233895aeddc444a28cd87fa01a4ec6e1894
data/CHANGELOG.md CHANGED
@@ -1,209 +1,207 @@
1
- ## Rails 7.0.4 (September 09, 2022) ##
1
+ ## Rails 6.1.7.1 (January 17, 2023) ##
2
2
 
3
- * Handle name clashes in attribute methods code generation cache.
4
-
5
- When two distinct attribute methods would generate similar names,
6
- the first implementation would be incorrectly re-used.
7
-
8
- ```ruby
9
- class A
10
- attribute_method_suffix "_changed?"
11
- define_attribute_methods :x
12
- end
13
-
14
- class B
15
- attribute_method_suffix "?"
16
- define_attribute_methods :x_changed
17
- end
18
- ```
3
+ * No changes.
19
4
 
20
- *Jean Boussier*
21
5
 
22
- ## Rails 7.0.3.1 (July 12, 2022) ##
6
+ ## Rails 6.1.7 (September 09, 2022) ##
23
7
 
24
8
  * No changes.
25
9
 
26
10
 
27
- ## Rails 7.0.3 (May 09, 2022) ##
11
+ ## Rails 6.1.6.1 (July 12, 2022) ##
28
12
 
29
13
  * No changes.
30
14
 
31
15
 
32
- ## Rails 7.0.2.4 (April 26, 2022) ##
16
+ ## Rails 6.1.6 (May 09, 2022) ##
33
17
 
34
18
  * No changes.
35
19
 
36
20
 
37
- ## Rails 7.0.2.3 (March 08, 2022) ##
21
+ ## Rails 6.1.5.1 (April 26, 2022) ##
38
22
 
39
23
  * No changes.
40
24
 
41
25
 
42
- ## Rails 7.0.2.2 (February 11, 2022) ##
26
+ ## Rails 6.1.5 (March 09, 2022) ##
43
27
 
44
- * No changes.
28
+ * Clear secure password cache if password is set to `nil`
45
29
 
30
+ Before:
31
+
32
+ user.password = 'something'
33
+ user.password = nil
46
34
 
47
- ## Rails 7.0.2.1 (February 11, 2022) ##
35
+ user.password # => 'something'
48
36
 
49
- * No changes.
37
+ Now:
50
38
 
39
+ user.password = 'something'
40
+ user.password = nil
51
41
 
52
- ## Rails 7.0.2 (February 08, 2022) ##
42
+ user.password # => nil
53
43
 
54
- * Use different cache namespace for proxy calls
44
+ *Markus Doits*
55
45
 
56
- Models can currently have different attribute bodies for the same method
57
- names, leading to conflicts. Adding a new namespace `:active_model_proxy`
58
- fixes the issue.
46
+ * Fix delegation in `ActiveModel::Type::Registry#lookup` and `ActiveModel::Type.lookup`
59
47
 
60
- *Chris Salzberg*
48
+ Passing a last positional argument `{}` would be incorrectly considered as keyword argument.
61
49
 
50
+ *Benoit Daloze*
62
51
 
63
- ## Rails 7.0.1 (January 06, 2022) ##
52
+ * Fix `to_json` after `changes_applied` for `ActiveModel::Dirty` object.
64
53
 
65
- * No changes.
54
+ *Ryuta Kamizono*
66
55
 
67
56
 
68
- ## Rails 7.0.0 (December 15, 2021) ##
57
+ ## Rails 6.1.4.7 (March 08, 2022) ##
69
58
 
70
59
  * No changes.
71
60
 
72
61
 
73
- ## Rails 7.0.0.rc3 (December 14, 2021) ##
62
+ ## Rails 6.1.4.6 (February 11, 2022) ##
74
63
 
75
64
  * No changes.
76
65
 
77
66
 
78
- ## Rails 7.0.0.rc2 (December 14, 2021) ##
67
+ ## Rails 6.1.4.5 (February 11, 2022) ##
79
68
 
80
69
  * No changes.
81
70
 
82
- ## Rails 7.0.0.rc1 (December 06, 2021) ##
83
71
 
84
- * Remove support to Marshal load Rails 5.x `ActiveModel::AttributeSet` format.
72
+ ## Rails 6.1.4.4 (December 15, 2021) ##
85
73
 
86
- *Rafael Mendonça França*
74
+ * No changes.
87
75
 
88
- * Remove support to Marshal and YAML load Rails 5.x error format.
89
76
 
90
- *Rafael Mendonça França*
77
+ ## Rails 6.1.4.3 (December 14, 2021) ##
91
78
 
92
- * Remove deprecated support to use `[]=` in `ActiveModel::Errors#messages`.
79
+ * No changes.
93
80
 
94
- *Rafael Mendonça França*
95
81
 
96
- * Remove deprecated support to `delete` errors from `ActiveModel::Errors#messages`.
82
+ ## Rails 6.1.4.2 (December 14, 2021) ##
97
83
 
98
- *Rafael Mendonça França*
84
+ * No changes.
99
85
 
100
- * Remove deprecated support to `clear` errors from `ActiveModel::Errors#messages`.
101
86
 
102
- *Rafael Mendonça França*
87
+ ## Rails 6.1.4.1 (August 19, 2021) ##
103
88
 
104
- * Remove deprecated support concat errors to `ActiveModel::Errors#messages`.
89
+ * No changes.
105
90
 
106
- *Rafael Mendonça França*
107
91
 
108
- * Remove deprecated `ActiveModel::Errors#to_xml`.
92
+ ## Rails 6.1.4 (June 24, 2021) ##
109
93
 
110
- *Rafael Mendonça França*
94
+ * Fix `to_json` for `ActiveModel::Dirty` object.
111
95
 
112
- * Remove deprecated `ActiveModel::Errors#keys`.
96
+ Exclude +mutations_from_database+ attribute from json as it lead to recursion.
113
97
 
114
- *Rafael Mendonça França*
98
+ *Anil Maurya*
115
99
 
116
- * Remove deprecated `ActiveModel::Errors#values`.
117
100
 
118
- *Rafael Mendonça França*
101
+ ## Rails 6.1.3.2 (May 05, 2021) ##
119
102
 
120
- * Remove deprecated `ActiveModel::Errors#slice!`.
103
+ * No changes.
121
104
 
122
- *Rafael Mendonça França*
123
105
 
124
- * Remove deprecated `ActiveModel::Errors#to_h`.
106
+ ## Rails 6.1.3.1 (March 26, 2021) ##
125
107
 
126
- *Rafael Mendonça França*
108
+ * No changes.
127
109
 
128
- * Remove deprecated enumeration of `ActiveModel::Errors` instances as a Hash.
129
110
 
130
- *Rafael Mendonça França*
111
+ ## Rails 6.1.3 (February 17, 2021) ##
131
112
 
132
- * Clear secure password cache if password is set to `nil`
113
+ * No changes.
133
114
 
134
- Before:
135
115
 
136
- user.password = 'something'
137
- user.password = nil
116
+ ## Rails 6.1.2.1 (February 10, 2021) ##
138
117
 
139
- user.password # => 'something'
118
+ * No changes.
140
119
 
141
- Now:
142
120
 
143
- user.password = 'something'
144
- user.password = nil
121
+ ## Rails 6.1.2 (February 09, 2021) ##
145
122
 
146
- user.password # => nil
123
+ * No changes.
147
124
 
148
- *Markus Doits*
149
125
 
150
- ## Rails 7.0.0.alpha2 (September 15, 2021) ##
126
+ ## Rails 6.1.1 (January 07, 2021) ##
151
127
 
152
128
  * No changes.
153
129
 
154
130
 
155
- ## Rails 7.0.0.alpha1 (September 15, 2021) ##
131
+ ## Rails 6.1.0 (December 09, 2020) ##
156
132
 
157
- * Introduce `ActiveModel::API`.
133
+ * Pass in `base` instead of `base_class` to Error.human_attribute_name
158
134
 
159
- Make `ActiveModel::API` the minimum API to talk with Action Pack and Action View.
160
- This will allow adding more functionality to `ActiveModel::Model`.
135
+ This is useful in cases where the `human_attribute_name` method depends
136
+ on other attributes' values of the class under validation to derive what the
137
+ attribute name should be.
161
138
 
162
- *Petrik de Heus*, *Nathaniel Watts*
139
+ *Filipe Sabella*
163
140
 
164
- * Fix dirty check for Float::NaN and BigDecimal::NaN.
141
+ * Deprecate marshalling load from legacy attributes format.
165
142
 
166
- Float::NaN and BigDecimal::NaN in Ruby are [special values](https://bugs.ruby-lang.org/issues/1720)
167
- and can't be compared with `==`.
143
+ *Ryuta Kamizono*
168
144
 
169
- *Marcelo Lauxen*
145
+ * `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
170
146
 
171
- * Fix `to_json` for `ActiveModel::Dirty` object.
147
+ topic.update!(status: :archived)
148
+ topic.status_previously_changed?(from: "active", to: "archived")
149
+ # => true
172
150
 
173
- Exclude `mutations_from_database` attribute from json as it lead to recursion.
151
+ *George Claghorn*
174
152
 
175
- *Anil Maurya*
153
+ * Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
176
154
 
177
- * Add `ActiveModel::AttributeSet#values_for_database`.
155
+ class Animal
156
+ include ActiveModel::Attributes
157
+ attribute :age
158
+ end
178
159
 
179
- Returns attributes with values for assignment to the database.
160
+ animal = Animal.new
161
+ animal.freeze
162
+ animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
180
163
 
181
- *Chris Salzberg*
164
+ *Josh Brody*
182
165
 
183
- * Fix delegation in ActiveModel::Type::Registry#lookup and ActiveModel::Type.lookup.
166
+ * Add `*_previously_was` attribute methods when dirty tracking. Example:
184
167
 
185
- Passing a last positional argument `{}` would be incorrectly considered as keyword argument.
168
+ pirate.update(catchphrase: "Ahoy!")
169
+ pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"]
170
+ pirate.catchphrase_previously_was # => "Thar She Blows!"
186
171
 
187
- *Benoit Daloze*
172
+ *DHH*
188
173
 
189
- * Cache and re-use generated attribute methods.
174
+ * Encapsulate each validation error as an Error object.
190
175
 
191
- Generated methods with identical implementations will now share their instruction sequences
192
- leading to reduced memory retention, and slightly faster load time.
176
+ The `ActiveModel`’s `errors` collection is now an array of these Error
177
+ objects, instead of messages/details hash.
193
178
 
194
- *Jean Boussier*
179
+ For each of these `Error` object, its `message` and `full_message` methods
180
+ are for generating error messages. Its `details` method would return error’s
181
+ extra parameters, found in the original `details` hash.
195
182
 
196
- * Add `in: range` parameter to `numericality` validator.
183
+ The change tries its best at maintaining backward compatibility, however
184
+ some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating
185
+ `errors.messages` and `errors.details` hashes directly will have no effect. Moving forward,
186
+ please convert those direct manipulations to use provided API methods instead.
187
+ Please note that `errors#add` now accepts `options` as keyword arguments instead of `Hash` which
188
+ introduced a change in Ruby 3 to [keyword arguments][kwargs-ann].
197
189
 
198
- *Michal Papis*
190
+ [kwargs-ann]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
199
191
 
200
- * Add `locale` argument to `ActiveModel::Name#initialize` to be used to generate the `singular`,
201
- `plural`, `route_key` and `singular_route_key` values.
192
+ The list of deprecated methods and their planned future behavioral changes at the next major release are:
202
193
 
203
- *Lukas Pokorny*
204
-
205
- * Make ActiveModel::Errors#inspect slimmer for readability
194
+ * `errors#slice!` will be removed.
195
+ * `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object.
196
+ * `errors#values` will be removed.
197
+ * `errors#keys` will be removed.
198
+ * `errors#to_xml` will be removed.
199
+ * `errors#to_h` will be removed, and can be replaced with `errors#to_hash`.
200
+ * Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`).
201
+ * Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect.
202
+ * Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect.
206
203
 
207
204
  *lulalala*
208
205
 
209
- Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/activemodel/CHANGELOG.md) for previous changes.
206
+
207
+ Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -18,4 +18,3 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
-
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::API</tt>.
19
+ to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
20
20
 
21
21
  class Person
22
- include ActiveModel::API
22
+ include ActiveModel::Model
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::API</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::API</tt> for more examples.
35
+ See <tt>ActiveModel::Model</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:
@@ -56,10 +56,6 @@ module ActiveModel
56
56
  type.serialize(value)
57
57
  end
58
58
 
59
- def serializable?(&block)
60
- type.serializable?(value, &block)
61
- end
62
-
63
59
  def changed?
64
60
  changed_from_assignment? || changed_in_place?
65
61
  end
@@ -67,7 +67,6 @@ 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"
71
70
 
72
71
  included do
73
72
  class_attribute :attribute_aliases, instance_writer: false, default: {}
@@ -106,8 +105,8 @@ module ActiveModel
106
105
  # person.name # => "Bob"
107
106
  # person.clear_name
108
107
  # person.name # => nil
109
- def attribute_method_prefix(*prefixes, parameters: nil)
110
- self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new(prefix: prefix, parameters: parameters) }
108
+ def attribute_method_prefix(*prefixes)
109
+ self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
111
110
  undefine_attribute_methods
112
111
  end
113
112
 
@@ -141,8 +140,8 @@ module ActiveModel
141
140
  # person.name = 'Bob'
142
141
  # person.name # => "Bob"
143
142
  # person.name_short? # => true
144
- def attribute_method_suffix(*suffixes, parameters: nil)
145
- self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new(suffix: suffix, parameters: parameters) }
143
+ def attribute_method_suffix(*suffixes)
144
+ self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
146
145
  undefine_attribute_methods
147
146
  end
148
147
 
@@ -178,7 +177,7 @@ module ActiveModel
178
177
  # person.reset_name_to_default!
179
178
  # person.name # => 'Default Name'
180
179
  def attribute_method_affix(*affixes)
181
- self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new(**affix) }
180
+ self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
182
181
  undefine_attribute_methods
183
182
  end
184
183
 
@@ -208,33 +207,11 @@ module ActiveModel
208
207
  # person.nickname_short? # => true
209
208
  def alias_attribute(new_name, old_name)
210
209
  self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
211
- ActiveSupport::CodeGenerator.batch(self, __FILE__, __LINE__) do |code_generator|
210
+ CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
212
211
  attribute_method_matchers.each do |matcher|
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
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
238
215
  end
239
216
  end
240
217
  end
@@ -253,7 +230,7 @@ module ActiveModel
253
230
  # <tt>ActiveModel::AttributeMethods</tt>.
254
231
  #
255
232
  # To use, pass attribute names (as strings or symbols). Be sure to declare
256
- # +define_attribute_methods+ after you define any prefix, suffix, or affix
233
+ # +define_attribute_methods+ after you define any prefix, suffix or affix
257
234
  # methods, or they will not hook in.
258
235
  #
259
236
  # class Person
@@ -274,7 +251,7 @@ module ActiveModel
274
251
  # end
275
252
  # end
276
253
  def define_attribute_methods(*attr_names)
277
- ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
254
+ CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
278
255
  attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
279
256
  end
280
257
  end
@@ -309,7 +286,7 @@ module ActiveModel
309
286
  # person.name # => "Bob"
310
287
  # person.name_short? # => true
311
288
  def define_attribute_method(attr_name, _owner: generated_attribute_methods)
312
- ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
289
+ CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
313
290
  attribute_method_matchers.each do |matcher|
314
291
  method_name = matcher.method_name(attr_name)
315
292
 
@@ -319,7 +296,7 @@ module ActiveModel
319
296
  if respond_to?(generate_method, true)
320
297
  send(generate_method, attr_name.to_s, owner: owner)
321
298
  else
322
- define_proxy_call(owner, method_name, matcher.target, matcher.parameters, attr_name.to_s, namespace: :active_model_proxy)
299
+ define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s
323
300
  end
324
301
  end
325
302
  end
@@ -358,6 +335,46 @@ module ActiveModel
358
335
  end
359
336
 
360
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
+
361
378
  def generated_attribute_methods
362
379
  @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
363
380
  end
@@ -381,48 +398,42 @@ module ActiveModel
381
398
 
382
399
  def attribute_method_matchers_matching(method_name)
383
400
  attribute_method_matchers_cache.compute_if_absent(method_name) do
384
- attribute_method_matchers.filter_map { |matcher| matcher.match(method_name) }
401
+ attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
385
402
  end
386
403
  end
387
404
 
388
405
  # Define a method `name` in `mod` that dispatches to `send`
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*")}"
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|"
395
413
  end
396
414
 
397
- code_generator.define_cached_method(name, as: mangled_name, namespace: :"#{namespace}_#{target}") do |batch|
398
- call_args.map!(&:inspect)
399
- call_args << parameters if parameters
400
-
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
415
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
407
416
 
408
- modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
409
-
410
- batch <<
411
- "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
412
- body <<
413
- "end"
417
+ body = if CALL_COMPILABLE_REGEXP.match?(target)
418
+ "#{"self." unless include_private}#{target}(#{extra})"
419
+ else
420
+ "send(:'#{target}', #{extra})"
414
421
  end
422
+
423
+ code_generator <<
424
+ defn <<
425
+ body <<
426
+ "end" <<
427
+ "ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
415
428
  end
416
429
 
417
- class AttributeMethodMatcher # :nodoc:
418
- attr_reader :prefix, :suffix, :target, :parameters
430
+ class AttributeMethodMatcher #:nodoc:
431
+ attr_reader :prefix, :suffix, :target
419
432
 
420
433
  AttributeMethodMatch = Struct.new(:target, :attr_name)
421
434
 
422
- def initialize(prefix: "", suffix: "", parameters: nil)
423
- @prefix = prefix
424
- @suffix = suffix
425
- @parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
435
+ def initialize(options = {})
436
+ @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
426
437
  @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
427
438
  @target = "#{@prefix}attribute#{@suffix}"
428
439
  @method_name = "#{prefix}%s#{suffix}"
@@ -458,7 +469,7 @@ module ActiveModel
458
469
  match ? attribute_missing(match, *args, &block) : super
459
470
  end
460
471
  end
461
- ruby2_keywords(:method_missing)
472
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
462
473
 
463
474
  # +attribute_missing+ is like +method_missing+, but for attributes. When
464
475
  # +method_missing+ is called we check to see if there is a matching
@@ -467,7 +478,6 @@ module ActiveModel
467
478
  def attribute_missing(match, *args, &block)
468
479
  __send__(match.target, match.attr_name, *args, &block)
469
480
  end
470
- ruby2_keywords(:attribute_missing)
471
481
 
472
482
  # A +Person+ instance with a +name+ attribute can ask
473
483
  # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
@@ -510,6 +520,10 @@ module ActiveModel
510
520
 
511
521
  # We want to generate the methods via module_eval rather than
512
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.
513
527
  #
514
528
  # But sometimes the database might return columns with
515
529
  # characters that are not allowed in normal method names (like
@@ -533,6 +547,7 @@ module ActiveModel
533
547
  temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
534
548
  attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
535
549
  yield temp_method_name, attr_name_expr
550
+ owner.rename_method(temp_method_name, method_name)
536
551
  end
537
552
  end
538
553
  end
@@ -144,7 +144,16 @@ module ActiveModel
144
144
  end
145
145
 
146
146
  def marshal_load(values)
147
- initialize(*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 7.0.
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
148
157
  end
149
158
 
150
159
  protected
@@ -25,10 +25,6 @@ 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
-
32
28
  def to_hash
33
29
  keys.index_with { |name| self[name].value }
34
30
  end
@@ -58,6 +54,7 @@ module ActiveModel
58
54
 
59
55
  def write_cast_value(name, value)
60
56
  @attributes[name] = self[name].with_cast_value(value)
57
+ value
61
58
  end
62
59
 
63
60
  def freeze