activemodel 6.0.6 → 6.1.0.rc1

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +48 -267
  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: 1195ea2c6b7aa374ae69cd558435ff5ad4ecdd2e4565674af49f3767b68c6062
4
- data.tar.gz: aae0aa3f548362fdb51f9defeefd76efe7a149be9659c70926fcf36265414b2e
3
+ metadata.gz: 81dfc9ef6f8512bfd2bb63155c8ffb85b9369bf763c09ac84c01c59619c17d67
4
+ data.tar.gz: 6d63d2c7bf9775e33be61539d1bda4d5d6dff0270649601b5c83f4ee6fcd0b6a
5
5
  SHA512:
6
- metadata.gz: bde873582e12e793157b29208ef90a83ce65d27b44b3e022f53ba978b7b76bbc4c179c96dffea9a818cb23d89bbde03ccdcf5dfdc494485cb0b7cf38d82d2597
7
- data.tar.gz: 7139b42b5fa588045bfcefd8eed459dc685842cc817985231219c0572dbd30700257fab9957420f7be4aa3f4f0b2c556606bbbe3cc53bf912ae9ea89fd117fbe
6
+ metadata.gz: e3941d0685fbc36e47f603f6c5322460e9da9e6a414a2e95f71a867f5dbbf1df14f7e673247319e49984560e5d620f9b840560301c38f0183fff44503a85b571
7
+ data.tar.gz: c0a52dc12a6aaf4c16ba296641e3a0729b00ebdc158ed3ba8158d748f022ad3dabfeff9bc159d0c96563e8e96bc54c594f37056b77a95a3fe72a9e6777330999
data/CHANGELOG.md CHANGED
@@ -1,292 +1,73 @@
1
- ## Rails 6.0.6 (September 09, 2022) ##
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.5.1 (July 12, 2022) ##
9
+ *Filipe Sabella*
7
10
 
8
- * No changes.
9
-
10
-
11
- ## Rails 6.0.5 (May 09, 2022) ##
12
-
13
- * No changes.
14
-
15
-
16
- ## Rails 6.0.4.8 (April 26, 2022) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 6.0.4.7 (March 08, 2022) ##
22
-
23
- * No changes.
24
-
25
-
26
- ## Rails 6.0.4.6 (February 11, 2022) ##
27
-
28
- * No changes.
29
-
30
-
31
- ## Rails 6.0.4.5 (February 11, 2022) ##
32
-
33
- * No changes.
34
-
35
-
36
- ## Rails 6.0.4.4 (December 15, 2021) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 6.0.4.3 (December 14, 2021) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 6.0.4.2 (December 14, 2021) ##
47
-
48
- * No changes.
49
-
50
-
51
- ## Rails 6.0.4.1 (August 19, 2021) ##
52
-
53
- * No changes.
54
-
55
-
56
- ## Rails 6.0.4 (June 15, 2021) ##
57
-
58
- * No changes.
59
-
60
-
61
- ## Rails 6.0.3.7 (May 05, 2021) ##
62
-
63
- * No changes.
64
-
65
-
66
- ## Rails 6.0.3.6 (March 26, 2021) ##
67
-
68
- * No changes.
69
-
70
-
71
- ## Rails 6.0.3.5 (February 10, 2021) ##
72
-
73
- * No changes.
74
-
75
-
76
- ## Rails 6.0.3.4 (October 07, 2020) ##
77
-
78
- * No changes.
79
-
80
-
81
- ## Rails 6.0.3.3 (September 09, 2020) ##
82
-
83
- * No changes.
84
-
85
-
86
- ## Rails 6.0.3.2 (June 17, 2020) ##
87
-
88
- * No changes.
89
-
90
-
91
- ## Rails 6.0.3.1 (May 18, 2020) ##
92
-
93
- * No changes.
94
-
95
-
96
- ## Rails 6.0.3 (May 06, 2020) ##
97
-
98
- * No changes.
99
-
100
-
101
- ## Rails 6.0.2.2 (March 19, 2020) ##
102
-
103
- * No changes.
104
-
105
-
106
- ## Rails 6.0.2.1 (December 18, 2019) ##
107
-
108
- * No changes.
109
-
110
-
111
- ## Rails 6.0.2 (December 13, 2019) ##
112
-
113
- * No changes.
114
-
115
-
116
- ## Rails 6.0.1 (November 5, 2019) ##
117
-
118
- * No changes.
119
-
120
-
121
- ## Rails 6.0.0 (August 16, 2019) ##
122
-
123
- * No changes.
124
-
125
-
126
- ## Rails 6.0.0.rc2 (July 22, 2019) ##
127
-
128
- * No changes.
129
-
130
-
131
- ## Rails 6.0.0.rc1 (April 24, 2019) ##
132
-
133
- * Type cast falsy boolean symbols on boolean attribute as false.
134
-
135
- Fixes #35676.
11
+ * Deprecate marshalling load from legacy attributes format.
136
12
 
137
13
  *Ryuta Kamizono*
138
14
 
139
- * Change how validation error translation strings are fetched: The new behavior
140
- will first try the more specific keys, including doing locale fallback, then try
141
- the less specific ones.
142
-
143
- For example, this is the order in which keys will now be tried for a `blank`
144
- error on a `product`'s `title` attribute with current locale set to `en-US`:
145
-
146
- en-US.activerecord.errors.models.product.attributes.title.blank
147
- en-US.activerecord.errors.models.product.blank
148
- en-US.activerecord.errors.messages.blank
149
-
150
- en.activerecord.errors.models.product.attributes.title.blank
151
- en.activerecord.errors.models.product.blank
152
- en.activerecord.errors.messages.blank
153
-
154
- en-US.errors.attributes.title.blank
155
- en-US.errors.messages.blank
156
-
157
- en.errors.attributes.title.blank
158
- en.errors.messages.blank
159
-
160
- *Hugo Vacher*
161
-
162
-
163
- ## Rails 6.0.0.beta3 (March 11, 2019) ##
164
-
165
- * No changes.
166
-
15
+ * `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
167
16
 
168
- ## Rails 6.0.0.beta2 (February 25, 2019) ##
17
+ topic.update!(status: :archived)
18
+ topic.status_previously_changed?(from: "active", to: "archived")
19
+ # => true
169
20
 
170
- * Fix date value when casting a multiparameter date hash to not convert
171
- from Gregorian date to Julian date.
21
+ *George Claghorn*
172
22
 
173
- Before:
23
+ * Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
174
24
 
175
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
176
- # => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
177
-
178
- After:
179
-
180
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
181
- # => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
182
-
183
- Fixes #28521.
184
-
185
- *Sayan Chakraborty*
186
-
187
- * Fix year value when casting a multiparameter time hash.
188
-
189
- When assigning a hash to a time attribute that's missing a year component
190
- (e.g. a `time_select` with `:ignore_date` set to `true`) then the year
191
- defaults to 1970 instead of the expected 2000. This results in the attribute
192
- changing as a result of the save.
193
-
194
- Before:
195
- ```
196
- event = Event.new(start_time: { 4 => 20, 5 => 30 })
197
- event.start_time # => 1970-01-01 20:30:00 UTC
198
- event.save
199
- event.reload
200
- event.start_time # => 2000-01-01 20:30:00 UTC
201
- ```
202
-
203
- After:
204
- ```
205
- event = Event.new(start_time: { 4 => 20, 5 => 30 })
206
- event.start_time # => 2000-01-01 20:30:00 UTC
207
- event.save
208
- event.reload
209
- event.start_time # => 2000-01-01 20:30:00 UTC
210
- ```
211
-
212
- *Andrew White*
213
-
214
-
215
- ## Rails 6.0.0.beta1 (January 18, 2019) ##
216
-
217
- * Internal calls to `human_attribute_name` on an `Active Model` now pass attributes as strings instead of symbols
218
- in some cases.
219
-
220
- This is in line with examples in Rails docs and puts the code in line with the intention -
221
- the potential use of strings or symbols.
222
-
223
- It is recommended to cast the attribute input to your desired type as if you you are overriding that methid.
224
-
225
- *Martin Larochelle*
226
-
227
- * Add `ActiveModel::Errors#of_kind?`.
228
-
229
- *bogdanvlviv*, *Rafael Mendonça França*
230
-
231
- * Fix numericality equality validation of `BigDecimal` and `Float`
232
- by casting to `BigDecimal` on both ends of the validation.
233
-
234
- *Gannon McGibbon*
235
-
236
- * Add `#slice!` method to `ActiveModel::Errors`.
237
-
238
- *Daniel López Prat*
239
-
240
- * Fix numericality validator to still use value before type cast except Active Record.
241
-
242
- Fixes #33651, #33686.
243
-
244
- *Ryuta Kamizono*
245
-
246
- * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps.
25
+ class Animal
26
+ include ActiveModel::Attributes
27
+ attribute :age
28
+ end
247
29
 
248
- Before:
249
- ```
250
- contact = Contact.new(created_at: Time.utc(2006, 8, 1))
251
- contact.as_json["created_at"] # => 2006-08-01 00:00:00 UTC
252
- ```
30
+ animal = Animal.new
31
+ animal.freeze
32
+ animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
253
33
 
254
- After:
255
- ```
256
- contact = Contact.new(created_at: Time.utc(2006, 8, 1))
257
- contact.as_json["created_at"] # => "2006-08-01T00:00:00.000Z"
258
- ```
34
+ *Josh Brody*
259
35
 
260
- *Bogdan Gusiev*
36
+ * Add `*_previously_was` attribute methods when dirty tracking. Example:
261
37
 
262
- * Allows configurable attribute name for `#has_secure_password`. This
263
- still defaults to an attribute named 'password', causing no breaking
264
- change. There is a new method `#authenticate_XXX` where XXX is the
265
- configured attribute name, making the existing `#authenticate` now an
266
- 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!"
267
41
 
268
- Example:
42
+ *DHH*
269
43
 
270
- class User < ActiveRecord::Base
271
- has_secure_password :recovery_password, validations: false
272
- end
44
+ * Encapsulate each validation error as an Error object.
273
45
 
274
- user = User.new()
275
- user.recovery_password = "42password"
276
- user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uX..."
277
- 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.
278
48
 
279
- *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.
280
52
 
281
- * Add `config.active_model.i18n_customize_full_message` in order to control whether
282
- the `full_message` error format can be overridden at the attribute or model
283
- 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.
284
57
 
285
- *Martin Larochelle*
58
+ The list of deprecated methods and their planned future behavioral changes at the next major release are:
286
59
 
287
- * 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.
288
69
 
289
- *Jeremy Daer*, *Kasper Timm Hansen*
70
+ *lulalala*
290
71
 
291
72
 
292
- 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