activemodel 6.0.6.1 → 6.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -272
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_model/attribute.rb +15 -14
  6. data/lib/active_model/attribute_assignment.rb +3 -4
  7. data/lib/active_model/attribute_methods.rb +74 -38
  8. data/lib/active_model/attribute_mutation_tracker.rb +8 -5
  9. data/lib/active_model/attribute_set/builder.rb +80 -13
  10. data/lib/active_model/attribute_set.rb +18 -16
  11. data/lib/active_model/attributes.rb +20 -24
  12. data/lib/active_model/dirty.rb +12 -4
  13. data/lib/active_model/error.rb +207 -0
  14. data/lib/active_model/errors.rb +316 -208
  15. data/lib/active_model/gem_version.rb +3 -3
  16. data/lib/active_model/lint.rb +1 -1
  17. data/lib/active_model/naming.rb +2 -2
  18. data/lib/active_model/nested_error.rb +22 -0
  19. data/lib/active_model/railtie.rb +1 -1
  20. data/lib/active_model/secure_password.rb +14 -14
  21. data/lib/active_model/serialization.rb +9 -6
  22. data/lib/active_model/serializers/json.rb +7 -0
  23. data/lib/active_model/type/date_time.rb +2 -2
  24. data/lib/active_model/type/float.rb +2 -0
  25. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
  26. data/lib/active_model/type/helpers/numeric.rb +8 -3
  27. data/lib/active_model/type/helpers/time_value.rb +27 -17
  28. data/lib/active_model/type/helpers/timezone.rb +1 -1
  29. data/lib/active_model/type/immutable_string.rb +14 -10
  30. data/lib/active_model/type/integer.rb +11 -2
  31. data/lib/active_model/type/registry.rb +5 -0
  32. data/lib/active_model/type/string.rb +12 -2
  33. data/lib/active_model/type/value.rb +9 -1
  34. data/lib/active_model/validations/absence.rb +1 -1
  35. data/lib/active_model/validations/acceptance.rb +1 -1
  36. data/lib/active_model/validations/clusivity.rb +5 -1
  37. data/lib/active_model/validations/confirmation.rb +2 -2
  38. data/lib/active_model/validations/exclusion.rb +1 -1
  39. data/lib/active_model/validations/format.rb +2 -2
  40. data/lib/active_model/validations/inclusion.rb +1 -1
  41. data/lib/active_model/validations/length.rb +2 -2
  42. data/lib/active_model/validations/numericality.rb +48 -41
  43. data/lib/active_model/validations/presence.rb +1 -1
  44. data/lib/active_model/validations/validates.rb +6 -4
  45. data/lib/active_model/validations.rb +2 -2
  46. data/lib/active_model/validator.rb +7 -2
  47. data/lib/active_model.rb +2 -1
  48. metadata +15 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 186b71a33dc404ff631b27cfdd72896a9d5781d07fba7f8238f399f03e9dcc17
4
- data.tar.gz: d0171f0cba7b4d82d9a27319e3eaf1ac06b4b580435c4792ce1f25f8ac619716
3
+ metadata.gz: 81dfc9ef6f8512bfd2bb63155c8ffb85b9369bf763c09ac84c01c59619c17d67
4
+ data.tar.gz: 6d63d2c7bf9775e33be61539d1bda4d5d6dff0270649601b5c83f4ee6fcd0b6a
5
5
  SHA512:
6
- metadata.gz: bbeefb791624e227b5b54f0da17c83f78194d750114e5e08a78f5ffc29d43dee7923a349b19cf56e95dc9b2e5488f04a2dc194585eed86ff58b3cdccda638ca2
7
- data.tar.gz: c2bb1200ffb43a7dce74be1812065d867752555bd6a9dfef4ae7b8d2f4907b8abb54d992635d5b883478bdf3af19ec5bfee73ed105fc20cbdd863fa6c412387a
6
+ metadata.gz: e3941d0685fbc36e47f603f6c5322460e9da9e6a414a2e95f71a867f5dbbf1df14f7e673247319e49984560e5d620f9b840560301c38f0183fff44503a85b571
7
+ data.tar.gz: c0a52dc12a6aaf4c16ba296641e3a0729b00ebdc158ed3ba8158d748f022ad3dabfeff9bc159d0c96563e8e96bc54c594f37056b77a95a3fe72a9e6777330999
data/CHANGELOG.md CHANGED
@@ -1,297 +1,73 @@
1
- ## Rails 6.0.6.1 (January 17, 2023) ##
1
+ ## Rails 6.1.0.rc1 (November 02, 2020) ##
2
2
 
3
- * No changes.
3
+ * Pass in `base` instead of `base_class` to Error.human_attribute_name
4
4
 
5
+ This is useful in cases where the `human_attribute_name` method depends
6
+ on other attributes' values of the class under validation to derive what the
7
+ attribute name should be.
5
8
 
6
- ## Rails 6.0.6 (September 09, 2022) ##
9
+ *Filipe Sabella*
7
10
 
8
- * No changes.
9
-
10
-
11
- ## Rails 6.0.5.1 (July 12, 2022) ##
12
-
13
- * No changes.
14
-
15
-
16
- ## Rails 6.0.5 (May 09, 2022) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 6.0.4.8 (April 26, 2022) ##
22
-
23
- * No changes.
24
-
25
-
26
- ## Rails 6.0.4.7 (March 08, 2022) ##
27
-
28
- * No changes.
29
-
30
-
31
- ## Rails 6.0.4.6 (February 11, 2022) ##
32
-
33
- * No changes.
34
-
35
-
36
- ## Rails 6.0.4.5 (February 11, 2022) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 6.0.4.4 (December 15, 2021) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 6.0.4.3 (December 14, 2021) ##
47
-
48
- * No changes.
49
-
50
-
51
- ## Rails 6.0.4.2 (December 14, 2021) ##
52
-
53
- * No changes.
54
-
55
-
56
- ## Rails 6.0.4.1 (August 19, 2021) ##
57
-
58
- * No changes.
59
-
60
-
61
- ## Rails 6.0.4 (June 15, 2021) ##
62
-
63
- * No changes.
64
-
65
-
66
- ## Rails 6.0.3.7 (May 05, 2021) ##
67
-
68
- * No changes.
69
-
70
-
71
- ## Rails 6.0.3.6 (March 26, 2021) ##
72
-
73
- * No changes.
74
-
75
-
76
- ## Rails 6.0.3.5 (February 10, 2021) ##
77
-
78
- * No changes.
79
-
80
-
81
- ## Rails 6.0.3.4 (October 07, 2020) ##
82
-
83
- * No changes.
84
-
85
-
86
- ## Rails 6.0.3.3 (September 09, 2020) ##
87
-
88
- * No changes.
89
-
90
-
91
- ## Rails 6.0.3.2 (June 17, 2020) ##
92
-
93
- * No changes.
94
-
95
-
96
- ## Rails 6.0.3.1 (May 18, 2020) ##
97
-
98
- * No changes.
99
-
100
-
101
- ## Rails 6.0.3 (May 06, 2020) ##
102
-
103
- * No changes.
104
-
105
-
106
- ## Rails 6.0.2.2 (March 19, 2020) ##
107
-
108
- * No changes.
109
-
110
-
111
- ## Rails 6.0.2.1 (December 18, 2019) ##
112
-
113
- * No changes.
114
-
115
-
116
- ## Rails 6.0.2 (December 13, 2019) ##
117
-
118
- * No changes.
119
-
120
-
121
- ## Rails 6.0.1 (November 5, 2019) ##
122
-
123
- * No changes.
124
-
125
-
126
- ## Rails 6.0.0 (August 16, 2019) ##
127
-
128
- * No changes.
129
-
130
-
131
- ## Rails 6.0.0.rc2 (July 22, 2019) ##
132
-
133
- * No changes.
134
-
135
-
136
- ## Rails 6.0.0.rc1 (April 24, 2019) ##
137
-
138
- * Type cast falsy boolean symbols on boolean attribute as false.
139
-
140
- Fixes #35676.
11
+ * Deprecate marshalling load from legacy attributes format.
141
12
 
142
13
  *Ryuta Kamizono*
143
14
 
144
- * Change how validation error translation strings are fetched: The new behavior
145
- will first try the more specific keys, including doing locale fallback, then try
146
- the less specific ones.
147
-
148
- For example, this is the order in which keys will now be tried for a `blank`
149
- error on a `product`'s `title` attribute with current locale set to `en-US`:
150
-
151
- en-US.activerecord.errors.models.product.attributes.title.blank
152
- en-US.activerecord.errors.models.product.blank
153
- en-US.activerecord.errors.messages.blank
154
-
155
- en.activerecord.errors.models.product.attributes.title.blank
156
- en.activerecord.errors.models.product.blank
157
- en.activerecord.errors.messages.blank
158
-
159
- en-US.errors.attributes.title.blank
160
- en-US.errors.messages.blank
161
-
162
- en.errors.attributes.title.blank
163
- en.errors.messages.blank
164
-
165
- *Hugo Vacher*
166
-
167
-
168
- ## Rails 6.0.0.beta3 (March 11, 2019) ##
169
-
170
- * No changes.
15
+ * `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
171
16
 
17
+ topic.update!(status: :archived)
18
+ topic.status_previously_changed?(from: "active", to: "archived")
19
+ # => true
172
20
 
173
- ## Rails 6.0.0.beta2 (February 25, 2019) ##
21
+ *George Claghorn*
174
22
 
175
- * Fix date value when casting a multiparameter date hash to not convert
176
- from Gregorian date to Julian date.
23
+ * Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
177
24
 
178
- Before:
179
-
180
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
181
- # => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
182
-
183
- After:
184
-
185
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
186
- # => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
187
-
188
- Fixes #28521.
189
-
190
- *Sayan Chakraborty*
191
-
192
- * Fix year value when casting a multiparameter time hash.
193
-
194
- When assigning a hash to a time attribute that's missing a year component
195
- (e.g. a `time_select` with `:ignore_date` set to `true`) then the year
196
- defaults to 1970 instead of the expected 2000. This results in the attribute
197
- changing as a result of the save.
198
-
199
- Before:
200
- ```
201
- event = Event.new(start_time: { 4 => 20, 5 => 30 })
202
- event.start_time # => 1970-01-01 20:30:00 UTC
203
- event.save
204
- event.reload
205
- event.start_time # => 2000-01-01 20:30:00 UTC
206
- ```
207
-
208
- After:
209
- ```
210
- event = Event.new(start_time: { 4 => 20, 5 => 30 })
211
- event.start_time # => 2000-01-01 20:30:00 UTC
212
- event.save
213
- event.reload
214
- event.start_time # => 2000-01-01 20:30:00 UTC
215
- ```
216
-
217
- *Andrew White*
218
-
219
-
220
- ## Rails 6.0.0.beta1 (January 18, 2019) ##
221
-
222
- * Internal calls to `human_attribute_name` on an `Active Model` now pass attributes as strings instead of symbols
223
- in some cases.
224
-
225
- This is in line with examples in Rails docs and puts the code in line with the intention -
226
- the potential use of strings or symbols.
227
-
228
- It is recommended to cast the attribute input to your desired type as if you you are overriding that methid.
229
-
230
- *Martin Larochelle*
231
-
232
- * Add `ActiveModel::Errors#of_kind?`.
233
-
234
- *bogdanvlviv*, *Rafael Mendonça França*
235
-
236
- * Fix numericality equality validation of `BigDecimal` and `Float`
237
- by casting to `BigDecimal` on both ends of the validation.
238
-
239
- *Gannon McGibbon*
240
-
241
- * Add `#slice!` method to `ActiveModel::Errors`.
242
-
243
- *Daniel López Prat*
244
-
245
- * Fix numericality validator to still use value before type cast except Active Record.
246
-
247
- Fixes #33651, #33686.
248
-
249
- *Ryuta Kamizono*
250
-
251
- * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps.
25
+ class Animal
26
+ include ActiveModel::Attributes
27
+ attribute :age
28
+ end
252
29
 
253
- Before:
254
- ```
255
- contact = Contact.new(created_at: Time.utc(2006, 8, 1))
256
- contact.as_json["created_at"] # => 2006-08-01 00:00:00 UTC
257
- ```
30
+ animal = Animal.new
31
+ animal.freeze
32
+ animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
258
33
 
259
- After:
260
- ```
261
- contact = Contact.new(created_at: Time.utc(2006, 8, 1))
262
- contact.as_json["created_at"] # => "2006-08-01T00:00:00.000Z"
263
- ```
34
+ *Josh Brody*
264
35
 
265
- *Bogdan Gusiev*
36
+ * Add `*_previously_was` attribute methods when dirty tracking. Example:
266
37
 
267
- * Allows configurable attribute name for `#has_secure_password`. This
268
- still defaults to an attribute named 'password', causing no breaking
269
- change. There is a new method `#authenticate_XXX` where XXX is the
270
- configured attribute name, making the existing `#authenticate` now an
271
- alias for this when the attribute is the default 'password'.
38
+ pirate.update(catchphrase: "Ahoy!")
39
+ pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"]
40
+ pirate.catchphrase_previously_was # => "Thar She Blows!"
272
41
 
273
- Example:
42
+ *DHH*
274
43
 
275
- class User < ActiveRecord::Base
276
- has_secure_password :recovery_password, validations: false
277
- end
44
+ * Encapsulate each validation error as an Error object.
278
45
 
279
- user = User.new()
280
- user.recovery_password = "42password"
281
- user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uX..."
282
- user.authenticate_recovery_password('42password') # => user
46
+ The `ActiveModel`’s `errors` collection is now an array of these Error
47
+ objects, instead of messages/details hash.
283
48
 
284
- *Unathi Chonco*
49
+ For each of these `Error` object, its `message` and `full_message` methods
50
+ are for generating error messages. Its `details` method would return error’s
51
+ extra parameters, found in the original `details` hash.
285
52
 
286
- * Add `config.active_model.i18n_customize_full_message` in order to control whether
287
- the `full_message` error format can be overridden at the attribute or model
288
- level in the locale files. This is `false` by default.
53
+ The change tries its best at maintaining backward compatibility, however
54
+ some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating
55
+ `errors.messages` and `errors.details` hashes directly will have no effect. Moving forward,
56
+ please convert those direct manipulations to use provided API methods instead.
289
57
 
290
- *Martin Larochelle*
58
+ The list of deprecated methods and their planned future behavioral changes at the next major release are:
291
59
 
292
- * Rails 6 requires Ruby 2.5.0 or newer.
60
+ * `errors#slice!` will be removed.
61
+ * `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object.
62
+ * `errors#values` will be removed.
63
+ * `errors#keys` will be removed.
64
+ * `errors#to_xml` will be removed.
65
+ * `errors#to_h` will be removed, and can be replaced with `errors#to_hash`.
66
+ * Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`).
67
+ * Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect.
68
+ * Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect.
293
69
 
294
- *Jeremy Daer*, *Kasper Timm Hansen*
70
+ *lulalala*
295
71
 
296
72
 
297
- Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md) for previous changes.
73
+ Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2019 David Heinemeier Hansson
1
+ Copyright (c) 2004-2020 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
@@ -200,7 +200,7 @@ behavior out of the box:
200
200
  attr_accessor :first_name, :last_name
201
201
 
202
202
  validates_each :first_name, :last_name do |record, attr, value|
203
- record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
203
+ record.errors.add attr, "starts with z." if value.start_with?("z")
204
204
  end
205
205
  end
206
206
 
@@ -241,7 +241,7 @@ The latest version of Active Model can be installed with RubyGems:
241
241
 
242
242
  Source code can be downloaded as part of the Rails project on GitHub
243
243
 
244
- * https://github.com/rails/rails/tree/main/activemodel
244
+ * https://github.com/rails/rails/tree/master/activemodel
245
245
 
246
246
 
247
247
  == License
@@ -5,16 +5,16 @@ require "active_support/core_ext/object/duplicable"
5
5
  module ActiveModel
6
6
  class Attribute # :nodoc:
7
7
  class << self
8
- def from_database(name, value, type)
9
- FromDatabase.new(name, value, type)
8
+ def from_database(name, value_before_type_cast, type, value = nil)
9
+ FromDatabase.new(name, value_before_type_cast, type, nil, value)
10
10
  end
11
11
 
12
- def from_user(name, value, type, original_attribute = nil)
13
- FromUser.new(name, value, type, original_attribute)
12
+ def from_user(name, value_before_type_cast, type, original_attribute = nil)
13
+ FromUser.new(name, value_before_type_cast, type, original_attribute)
14
14
  end
15
15
 
16
- def with_cast_value(name, value, type)
17
- WithCastValue.new(name, value, type)
16
+ def with_cast_value(name, value_before_type_cast, type)
17
+ WithCastValue.new(name, value_before_type_cast, type)
18
18
  end
19
19
 
20
20
  def null(name)
@@ -30,11 +30,12 @@ module ActiveModel
30
30
 
31
31
  # This method should not be called directly.
32
32
  # Use #from_database or #from_user
33
- def initialize(name, value_before_type_cast, type, original_attribute = nil)
33
+ def initialize(name, value_before_type_cast, type, original_attribute = nil, value = nil)
34
34
  @name = name
35
35
  @value_before_type_cast = value_before_type_cast
36
36
  @type = type
37
37
  @original_attribute = original_attribute
38
+ @value = value unless value.nil?
38
39
  end
39
40
 
40
41
  def value
@@ -132,14 +133,13 @@ module ActiveModel
132
133
  coder["value"] = value if defined?(@value)
133
134
  end
134
135
 
135
- protected
136
- def original_value_for_database
137
- if assigned?
138
- original_attribute.original_value_for_database
139
- else
140
- _original_value_for_database
141
- end
136
+ def original_value_for_database
137
+ if assigned?
138
+ original_attribute.original_value_for_database
139
+ else
140
+ _original_value_for_database
142
141
  end
142
+ end
143
143
 
144
144
  private
145
145
  attr_reader :original_attribute
@@ -167,6 +167,7 @@ module ActiveModel
167
167
  def _original_value_for_database
168
168
  value_before_type_cast
169
169
  end
170
+ private :_original_value_for_database
170
171
  end
171
172
 
172
173
  class FromUser < Attribute # :nodoc:
@@ -26,13 +26,12 @@ module ActiveModel
26
26
  # cat.name # => 'Gorby'
27
27
  # cat.status # => 'sleeping'
28
28
  def assign_attributes(new_attributes)
29
- if !new_attributes.respond_to?(:stringify_keys)
29
+ unless new_attributes.respond_to?(:each_pair)
30
30
  raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
31
31
  end
32
32
  return if new_attributes.empty?
33
33
 
34
- attributes = new_attributes.stringify_keys
35
- _assign_attributes(sanitize_for_mass_assignment(attributes))
34
+ _assign_attributes(sanitize_for_mass_assignment(new_attributes))
36
35
  end
37
36
 
38
37
  alias attributes= assign_attributes
@@ -49,7 +48,7 @@ module ActiveModel
49
48
  if respond_to?(setter)
50
49
  public_send(setter, v)
51
50
  else
52
- raise UnknownAttributeError.new(self, k)
51
+ raise UnknownAttributeError.new(self, k.to_s)
53
52
  end
54
53
  end
55
54
  end
@@ -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.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.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,18 +398,14 @@ 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
- # Bump plain matcher to last place so that only methods that do not
356
- # match any other pattern match the actual attribute name.
357
- # This is currently only needed to support legacy usage.
358
- matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
359
- matchers.map { |matcher| matcher.match(method_name) }.compact
401
+ attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
360
402
  end
361
403
  end
362
404
 
363
405
  # Define a method `name` in `mod` that dispatches to `send`
364
406
  # using the given `extra` args. This falls back on `define_method`
365
407
  # and `send` if the given names cannot be compiled.
366
- def define_proxy_call(include_private, mod, name, target, *extra)
408
+ def define_proxy_call(include_private, code_generator, name, target, *extra)
367
409
  defn = if NAME_COMPILABLE_REGEXP.match?(name)
368
410
  "def #{name}(*args)"
369
411
  else
@@ -378,12 +420,11 @@ module ActiveModel
378
420
  "send(:'#{target}', #{extra})"
379
421
  end
380
422
 
381
- mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
382
- #{defn}
383
- #{body}
384
- end
385
- ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)
386
- RUBY
423
+ code_generator <<
424
+ defn <<
425
+ body <<
426
+ "end" <<
427
+ "ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
387
428
  end
388
429
 
389
430
  class AttributeMethodMatcher #:nodoc:
@@ -407,10 +448,6 @@ module ActiveModel
407
448
  def method_name(attr_name)
408
449
  @method_name % attr_name
409
450
  end
410
-
411
- def plain?
412
- prefix.empty? && suffix.empty?
413
- end
414
451
  end
415
452
  end
416
453
 
@@ -499,10 +536,10 @@ module ActiveModel
499
536
  # to allocate an object on each call to the attribute method.
500
537
  # Making it frozen means that it doesn't get duped when used to
501
538
  # key the @attributes in read_attribute.
502
- def self.define_attribute_accessor_method(mod, attr_name, writer: false)
539
+ def self.define_attribute_accessor_method(owner, attr_name, writer: false)
503
540
  method_name = "#{attr_name}#{'=' if writer}"
504
541
  if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
505
- yield method_name, "'#{attr_name}'.freeze"
542
+ yield method_name, "'#{attr_name}'"
506
543
  else
507
544
  safe_name = attr_name.unpack1("h*")
508
545
  const_name = "ATTR_#{safe_name}"
@@ -510,8 +547,7 @@ module ActiveModel
510
547
  temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
511
548
  attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
512
549
  yield temp_method_name, attr_name_expr
513
- mod.alias_method method_name, temp_method_name
514
- mod.undef_method temp_method_name
550
+ owner.rename_method(temp_method_name, method_name)
515
551
  end
516
552
  end
517
553
  end