activemodel 6.0.3.4 → 6.1.2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -184
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_model.rb +2 -1
  6. data/lib/active_model/attribute.rb +18 -17
  7. data/lib/active_model/attribute_assignment.rb +3 -4
  8. data/lib/active_model/attribute_methods.rb +74 -38
  9. data/lib/active_model/attribute_mutation_tracker.rb +8 -5
  10. data/lib/active_model/attribute_set.rb +18 -16
  11. data/lib/active_model/attribute_set/builder.rb +80 -13
  12. data/lib/active_model/attributes.rb +20 -24
  13. data/lib/active_model/callbacks.rb +1 -1
  14. data/lib/active_model/dirty.rb +12 -4
  15. data/lib/active_model/error.rb +207 -0
  16. data/lib/active_model/errors.rb +316 -208
  17. data/lib/active_model/gem_version.rb +3 -3
  18. data/lib/active_model/lint.rb +1 -1
  19. data/lib/active_model/naming.rb +2 -2
  20. data/lib/active_model/nested_error.rb +22 -0
  21. data/lib/active_model/railtie.rb +1 -1
  22. data/lib/active_model/secure_password.rb +14 -14
  23. data/lib/active_model/serialization.rb +9 -6
  24. data/lib/active_model/serializers/json.rb +7 -0
  25. data/lib/active_model/type/date_time.rb +2 -2
  26. data/lib/active_model/type/float.rb +2 -0
  27. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
  28. data/lib/active_model/type/helpers/numeric.rb +8 -3
  29. data/lib/active_model/type/helpers/time_value.rb +27 -17
  30. data/lib/active_model/type/helpers/timezone.rb +1 -1
  31. data/lib/active_model/type/immutable_string.rb +14 -10
  32. data/lib/active_model/type/integer.rb +11 -2
  33. data/lib/active_model/type/registry.rb +11 -4
  34. data/lib/active_model/type/string.rb +12 -2
  35. data/lib/active_model/type/value.rb +9 -1
  36. data/lib/active_model/validations.rb +6 -6
  37. data/lib/active_model/validations/absence.rb +1 -1
  38. data/lib/active_model/validations/acceptance.rb +1 -1
  39. data/lib/active_model/validations/callbacks.rb +15 -15
  40. data/lib/active_model/validations/clusivity.rb +5 -1
  41. data/lib/active_model/validations/confirmation.rb +2 -2
  42. data/lib/active_model/validations/exclusion.rb +1 -1
  43. data/lib/active_model/validations/format.rb +2 -2
  44. data/lib/active_model/validations/inclusion.rb +1 -1
  45. data/lib/active_model/validations/length.rb +2 -2
  46. data/lib/active_model/validations/numericality.rb +54 -41
  47. data/lib/active_model/validations/presence.rb +1 -1
  48. data/lib/active_model/validations/validates.rb +6 -4
  49. data/lib/active_model/validator.rb +7 -1
  50. metadata +10 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86ec2634e2357d939729f9c02269a2bc92978e78896724276eaa97e35856999b
4
- data.tar.gz: 864dfd17b31fcd79df8a17a8fee1582693be410433ac950c588f609abf383782
3
+ metadata.gz: '08df17982afa4c16f01d3f9c07f60cc5817641626e071a40d6b9a3765672ba4a'
4
+ data.tar.gz: 51909d78d21a89d88a27278f6f19a7f97971c78d058be06261431f141e0c7392
5
5
  SHA512:
6
- metadata.gz: b157e2de3984dafcda3e589300bd91caa02a7de7fd5257d7078068a8bf98e2a850a461ed0e1e8a53e35999691c95bcd06f892394ab8de5b1ea012e730b40eca8
7
- data.tar.gz: f1a27fa1759dd46ce4dc16dc03c652d6aaf66b136e279253f2c2db5f8cc71620ef91306fe7d73bf0bb96d413f4ae42c8f0597f444ddc3465cdeb841c21846934
6
+ metadata.gz: b1968baa6cfe661631c0036dc4e544272f7ca3d1b136e93afa77e3c6c78605ce3c05196df85862f9a03eb612cb32ca65aacd392f0a13c6ecd58b0c9ae452fa3a
7
+ data.tar.gz: e038c7e4cad1354ce7a763e43b003a02b0470343494fbe0da45070c8f205d2f0bef4c3f9900fc492dc588b5a6331fc2bdc2b044d450e3a266cc32c502d98f00b
data/CHANGELOG.md CHANGED
@@ -1,217 +1,83 @@
1
- ## Rails 6.0.3.4 (October 07, 2020) ##
1
+ ## Rails 6.1.2 (February 09, 2021) ##
2
2
 
3
3
  * No changes.
4
4
 
5
5
 
6
- ## Rails 6.0.3.3 (September 09, 2020) ##
6
+ ## Rails 6.1.1 (January 07, 2021) ##
7
7
 
8
8
  * No changes.
9
9
 
10
10
 
11
- ## Rails 6.0.3.2 (June 17, 2020) ##
12
-
13
- * No changes.
14
-
15
-
16
- ## Rails 6.0.3.1 (May 18, 2020) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 6.0.3 (May 06, 2020) ##
22
-
23
- * No changes.
11
+ ## Rails 6.1.0 (December 09, 2020) ##
24
12
 
13
+ * Pass in `base` instead of `base_class` to Error.human_attribute_name
25
14
 
26
- ## Rails 6.0.2.2 (March 19, 2020) ##
15
+ This is useful in cases where the `human_attribute_name` method depends
16
+ on other attributes' values of the class under validation to derive what the
17
+ attribute name should be.
27
18
 
28
- * No changes.
29
-
30
-
31
- ## Rails 6.0.2.1 (December 18, 2019) ##
32
-
33
- * No changes.
34
-
35
-
36
- ## Rails 6.0.2 (December 13, 2019) ##
37
-
38
- * No changes.
19
+ *Filipe Sabella*
39
20
 
40
-
41
- ## Rails 6.0.1 (November 5, 2019) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 6.0.0 (August 16, 2019) ##
47
-
48
- * No changes.
49
-
50
-
51
- ## Rails 6.0.0.rc2 (July 22, 2019) ##
52
-
53
- * No changes.
54
-
55
-
56
- ## Rails 6.0.0.rc1 (April 24, 2019) ##
57
-
58
- * Type cast falsy boolean symbols on boolean attribute as false.
59
-
60
- Fixes #35676.
21
+ * Deprecate marshalling load from legacy attributes format.
61
22
 
62
23
  *Ryuta Kamizono*
63
24
 
64
- * Change how validation error translation strings are fetched: The new behavior
65
- will first try the more specific keys, including doing locale fallback, then try
66
- the less specific ones.
67
-
68
- For example, this is the order in which keys will now be tried for a `blank`
69
- error on a `product`'s `title` attribute with current locale set to `en-US`:
70
-
71
- en-US.activerecord.errors.models.product.attributes.title.blank
72
- en-US.activerecord.errors.models.product.blank
73
- en-US.activerecord.errors.messages.blank
74
-
75
- en.activerecord.errors.models.product.attributes.title.blank
76
- en.activerecord.errors.models.product.blank
77
- en.activerecord.errors.messages.blank
25
+ * `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
78
26
 
79
- en-US.errors.attributes.title.blank
80
- en-US.errors.messages.blank
27
+ topic.update!(status: :archived)
28
+ topic.status_previously_changed?(from: "active", to: "archived")
29
+ # => true
81
30
 
82
- en.errors.attributes.title.blank
83
- en.errors.messages.blank
31
+ *George Claghorn*
84
32
 
85
- *Hugo Vacher*
33
+ * Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
86
34
 
35
+ class Animal
36
+ include ActiveModel::Attributes
37
+ attribute :age
38
+ end
87
39
 
88
- ## Rails 6.0.0.beta3 (March 11, 2019) ##
89
-
90
- * No changes.
91
-
92
-
93
- ## Rails 6.0.0.beta2 (February 25, 2019) ##
94
-
95
- * Fix date value when casting a multiparameter date hash to not convert
96
- from Gregorian date to Julian date.
97
-
98
- Before:
99
-
100
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
101
- # => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
102
-
103
- After:
104
-
105
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
106
- # => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
107
-
108
- Fixes #28521.
109
-
110
- *Sayan Chakraborty*
111
-
112
- * Fix year value when casting a multiparameter time hash.
113
-
114
- When assigning a hash to a time attribute that's missing a year component
115
- (e.g. a `time_select` with `:ignore_date` set to `true`) then the year
116
- defaults to 1970 instead of the expected 2000. This results in the attribute
117
- changing as a result of the save.
118
-
119
- Before:
120
- ```
121
- event = Event.new(start_time: { 4 => 20, 5 => 30 })
122
- event.start_time # => 1970-01-01 20:30:00 UTC
123
- event.save
124
- event.reload
125
- event.start_time # => 2000-01-01 20:30:00 UTC
126
- ```
127
-
128
- After:
129
- ```
130
- event = Event.new(start_time: { 4 => 20, 5 => 30 })
131
- event.start_time # => 2000-01-01 20:30:00 UTC
132
- event.save
133
- event.reload
134
- event.start_time # => 2000-01-01 20:30:00 UTC
135
- ```
136
-
137
- *Andrew White*
138
-
139
-
140
- ## Rails 6.0.0.beta1 (January 18, 2019) ##
141
-
142
- * Internal calls to `human_attribute_name` on an `Active Model` now pass attributes as strings instead of symbols
143
- in some cases.
144
-
145
- This is in line with examples in Rails docs and puts the code in line with the intention -
146
- the potential use of strings or symbols.
147
-
148
- It is recommended to cast the attribute input to your desired type as if you you are overriding that methid.
149
-
150
- *Martin Larochelle*
151
-
152
- * Add `ActiveModel::Errors#of_kind?`.
153
-
154
- *bogdanvlviv*, *Rafael Mendonça França*
155
-
156
- * Fix numericality equality validation of `BigDecimal` and `Float`
157
- by casting to `BigDecimal` on both ends of the validation.
158
-
159
- *Gannon McGibbon*
160
-
161
- * Add `#slice!` method to `ActiveModel::Errors`.
162
-
163
- *Daniel López Prat*
164
-
165
- * Fix numericality validator to still use value before type cast except Active Record.
166
-
167
- Fixes #33651, #33686.
168
-
169
- *Ryuta Kamizono*
170
-
171
- * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps.
172
-
173
- Before:
174
- ```
175
- contact = Contact.new(created_at: Time.utc(2006, 8, 1))
176
- contact.as_json["created_at"] # => 2006-08-01 00:00:00 UTC
177
- ```
40
+ animal = Animal.new
41
+ animal.freeze
42
+ animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
178
43
 
179
- After:
180
- ```
181
- contact = Contact.new(created_at: Time.utc(2006, 8, 1))
182
- contact.as_json["created_at"] # => "2006-08-01T00:00:00.000Z"
183
- ```
44
+ *Josh Brody*
184
45
 
185
- *Bogdan Gusiev*
46
+ * Add `*_previously_was` attribute methods when dirty tracking. Example:
186
47
 
187
- * Allows configurable attribute name for `#has_secure_password`. This
188
- still defaults to an attribute named 'password', causing no breaking
189
- change. There is a new method `#authenticate_XXX` where XXX is the
190
- configured attribute name, making the existing `#authenticate` now an
191
- alias for this when the attribute is the default 'password'.
48
+ pirate.update(catchphrase: "Ahoy!")
49
+ pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"]
50
+ pirate.catchphrase_previously_was # => "Thar She Blows!"
192
51
 
193
- Example:
52
+ *DHH*
194
53
 
195
- class User < ActiveRecord::Base
196
- has_secure_password :recovery_password, validations: false
197
- end
54
+ * Encapsulate each validation error as an Error object.
198
55
 
199
- user = User.new()
200
- user.recovery_password = "42password"
201
- user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uX..."
202
- user.authenticate_recovery_password('42password') # => user
56
+ The `ActiveModel`’s `errors` collection is now an array of these Error
57
+ objects, instead of messages/details hash.
203
58
 
204
- *Unathi Chonco*
59
+ For each of these `Error` object, its `message` and `full_message` methods
60
+ are for generating error messages. Its `details` method would return error’s
61
+ extra parameters, found in the original `details` hash.
205
62
 
206
- * Add `config.active_model.i18n_customize_full_message` in order to control whether
207
- the `full_message` error format can be overridden at the attribute or model
208
- level in the locale files. This is `false` by default.
63
+ The change tries its best at maintaining backward compatibility, however
64
+ some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating
65
+ `errors.messages` and `errors.details` hashes directly will have no effect. Moving forward,
66
+ please convert those direct manipulations to use provided API methods instead.
209
67
 
210
- *Martin Larochelle*
68
+ The list of deprecated methods and their planned future behavioral changes at the next major release are:
211
69
 
212
- * Rails 6 requires Ruby 2.5.0 or newer.
70
+ * `errors#slice!` will be removed.
71
+ * `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object.
72
+ * `errors#values` will be removed.
73
+ * `errors#keys` will be removed.
74
+ * `errors#to_xml` will be removed.
75
+ * `errors#to_h` will be removed, and can be replaced with `errors#to_hash`.
76
+ * Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`).
77
+ * Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect.
78
+ * Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect.
213
79
 
214
- *Jeremy Daer*, *Kasper Timm Hansen*
80
+ *lulalala*
215
81
 
216
82
 
217
- Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md) for previous changes.
83
+ 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/master/activemodel
244
+ * https://github.com/rails/rails/tree/main/activemodel
245
245
 
246
246
 
247
247
  == License
data/lib/active_model.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2004-2019 David Heinemeier Hansson
4
+ # Copyright (c) 2004-2020 David Heinemeier Hansson
5
5
  #
6
6
  # Permission is hereby granted, free of charge, to any person obtaining
7
7
  # a copy of this software and associated documentation files (the
@@ -53,6 +53,7 @@ module ActiveModel
53
53
 
54
54
  eager_autoload do
55
55
  autoload :Errors
56
+ autoload :Error
56
57
  autoload :RangeError, "active_model/errors"
57
58
  autoload :StrictValidationFailed, "active_model/errors"
58
59
  autoload :UnknownAttributeError, "active_model/errors"
@@ -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
@@ -164,9 +164,10 @@ module ActiveModel
164
164
  type.deserialize(value)
165
165
  end
166
166
 
167
- def _original_value_for_database
168
- value_before_type_cast
169
- end
167
+ private
168
+ def _original_value_for_database
169
+ value_before_type_cast
170
+ end
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