activemodel 5.2.8.1 → 6.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -80
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -3
  5. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  6. data/lib/active_model/attribute.rb +3 -4
  7. data/lib/active_model/attribute_assignment.rb +1 -2
  8. data/lib/active_model/attribute_methods.rb +56 -15
  9. data/lib/active_model/attribute_mutation_tracker.rb +88 -34
  10. data/lib/active_model/attribute_set/builder.rb +1 -3
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  12. data/lib/active_model/attribute_set.rb +2 -12
  13. data/lib/active_model/attributes.rb +60 -35
  14. data/lib/active_model/callbacks.rb +10 -8
  15. data/lib/active_model/conversion.rb +1 -1
  16. data/lib/active_model/dirty.rb +36 -99
  17. data/lib/active_model/errors.rb +105 -21
  18. data/lib/active_model/gem_version.rb +4 -4
  19. data/lib/active_model/naming.rb +20 -5
  20. data/lib/active_model/railtie.rb +6 -0
  21. data/lib/active_model/secure_password.rb +47 -48
  22. data/lib/active_model/serialization.rb +0 -1
  23. data/lib/active_model/serializers/json.rb +10 -9
  24. data/lib/active_model/translation.rb +1 -1
  25. data/lib/active_model/type/big_integer.rb +0 -1
  26. data/lib/active_model/type/binary.rb +1 -1
  27. data/lib/active_model/type/boolean.rb +0 -1
  28. data/lib/active_model/type/date.rb +0 -5
  29. data/lib/active_model/type/date_time.rb +3 -8
  30. data/lib/active_model/type/decimal.rb +0 -1
  31. data/lib/active_model/type/float.rb +0 -3
  32. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +4 -0
  33. data/lib/active_model/type/helpers/numeric.rb +9 -3
  34. data/lib/active_model/type/helpers/time_value.rb +17 -5
  35. data/lib/active_model/type/immutable_string.rb +0 -1
  36. data/lib/active_model/type/integer.rb +8 -20
  37. data/lib/active_model/type/registry.rb +11 -16
  38. data/lib/active_model/type/string.rb +2 -3
  39. data/lib/active_model/type/time.rb +1 -6
  40. data/lib/active_model/type/value.rb +0 -1
  41. data/lib/active_model/validations/absence.rb +1 -1
  42. data/lib/active_model/validations/acceptance.rb +33 -26
  43. data/lib/active_model/validations/callbacks.rb +0 -1
  44. data/lib/active_model/validations/clusivity.rb +1 -2
  45. data/lib/active_model/validations/confirmation.rb +2 -2
  46. data/lib/active_model/validations/format.rb +1 -2
  47. data/lib/active_model/validations/inclusion.rb +1 -1
  48. data/lib/active_model/validations/length.rb +1 -1
  49. data/lib/active_model/validations/numericality.rb +5 -4
  50. data/lib/active_model/validations/validates.rb +2 -3
  51. data/lib/active_model/validations.rb +0 -3
  52. data/lib/active_model/validator.rb +1 -2
  53. data/lib/active_model.rb +1 -1
  54. metadata +12 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7f6668533dd90a9b8c479fc71940522acf40284b4604e8d689d637129e6795a
4
- data.tar.gz: 9a519747c7fc021167852457a0e03056bc68351001f61d366d32386afe584d11
3
+ metadata.gz: 1195ea2c6b7aa374ae69cd558435ff5ad4ecdd2e4565674af49f3767b68c6062
4
+ data.tar.gz: aae0aa3f548362fdb51f9defeefd76efe7a149be9659c70926fcf36265414b2e
5
5
  SHA512:
6
- metadata.gz: 0f5a6ba7be9164544db261136c14337a256ad42cf9776e8364b745a6ff2c90f32f5e7a53e24ddb6032971de7e6bca59cf27808dea11d060ab5c3b20b92baef9d
7
- data.tar.gz: b63d763a8de2b5438801b3698f33522cd68f37c22de007d752a4d1d9250b6f2ed31aab780cc07c4179670c863bb517286b85dd77ffb9cdb1624cbf5cbbe3935b
6
+ metadata.gz: bde873582e12e793157b29208ef90a83ce65d27b44b3e022f53ba978b7b76bbc4c179c96dffea9a818cb23d89bbde03ccdcf5dfdc494485cb0b7cf38d82d2597
7
+ data.tar.gz: 7139b42b5fa588045bfcefd8eed459dc685842cc817985231219c0572dbd30700257fab9957420f7be4aa3f4f0b2c556606bbbe3cc53bf912ae9ea89fd117fbe
data/CHANGELOG.md CHANGED
@@ -1,79 +1,134 @@
1
- ## Rails 5.2.8.1 (July 12, 2022) ##
1
+ ## Rails 6.0.6 (September 09, 2022) ##
2
2
 
3
3
  * No changes.
4
4
 
5
5
 
6
- ## Rails 5.2.8 (May 09, 2022) ##
6
+ ## Rails 6.0.5.1 (July 12, 2022) ##
7
7
 
8
8
  * No changes.
9
9
 
10
10
 
11
- ## Rails 5.2.7.1 (April 26, 2022) ##
11
+ ## Rails 6.0.5 (May 09, 2022) ##
12
12
 
13
13
  * No changes.
14
14
 
15
15
 
16
- ## Rails 5.2.7 (March 10, 2022) ##
16
+ ## Rails 6.0.4.8 (April 26, 2022) ##
17
17
 
18
18
  * No changes.
19
19
 
20
20
 
21
- ## Rails 5.2.6.3 (March 08, 2022) ##
21
+ ## Rails 6.0.4.7 (March 08, 2022) ##
22
22
 
23
23
  * No changes.
24
24
 
25
25
 
26
- ## Rails 5.2.6.2 (February 11, 2022) ##
26
+ ## Rails 6.0.4.6 (February 11, 2022) ##
27
27
 
28
28
  * No changes.
29
29
 
30
30
 
31
- ## Rails 5.2.6.1 (February 11, 2022) ##
31
+ ## Rails 6.0.4.5 (February 11, 2022) ##
32
32
 
33
33
  * No changes.
34
34
 
35
35
 
36
- ## Rails 5.2.6 (May 05, 2021) ##
36
+ ## Rails 6.0.4.4 (December 15, 2021) ##
37
37
 
38
38
  * No changes.
39
39
 
40
40
 
41
- ## Rails 5.2.5 (March 26, 2021) ##
41
+ ## Rails 6.0.4.3 (December 14, 2021) ##
42
42
 
43
43
  * No changes.
44
44
 
45
45
 
46
- ## Rails 5.2.4.6 (May 05, 2021) ##
46
+ ## Rails 6.0.4.2 (December 14, 2021) ##
47
47
 
48
48
  * No changes.
49
49
 
50
50
 
51
- ## Rails 5.2.4.5 (February 10, 2021) ##
51
+ ## Rails 6.0.4.1 (August 19, 2021) ##
52
52
 
53
53
  * No changes.
54
54
 
55
55
 
56
- ## Rails 5.2.4.4 (September 09, 2020) ##
56
+ ## Rails 6.0.4 (June 15, 2021) ##
57
57
 
58
58
  * No changes.
59
59
 
60
60
 
61
- ## Rails 5.2.4.3 (May 18, 2020) ##
61
+ ## Rails 6.0.3.7 (May 05, 2021) ##
62
62
 
63
63
  * No changes.
64
64
 
65
65
 
66
- ## Rails 5.2.4.2 (March 19, 2020) ##
66
+ ## Rails 6.0.3.6 (March 26, 2021) ##
67
67
 
68
68
  * No changes.
69
69
 
70
70
 
71
- ## Rails 5.2.4.1 (December 18, 2019) ##
71
+ ## Rails 6.0.3.5 (February 10, 2021) ##
72
72
 
73
73
  * No changes.
74
74
 
75
75
 
76
- ## Rails 5.2.4 (November 27, 2019) ##
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) ##
77
132
 
78
133
  * Type cast falsy boolean symbols on boolean attribute as false.
79
134
 
@@ -81,8 +136,36 @@
81
136
 
82
137
  *Ryuta Kamizono*
83
138
 
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*
84
161
 
85
- ## Rails 5.2.3 (March 27, 2019) ##
162
+
163
+ ## Rails 6.0.0.beta3 (March 11, 2019) ##
164
+
165
+ * No changes.
166
+
167
+
168
+ ## Rails 6.0.0.beta2 (February 25, 2019) ##
86
169
 
87
170
  * Fix date value when casting a multiparameter date hash to not convert
88
171
  from Gregorian date to Julian date.
@@ -90,109 +173,120 @@
90
173
  Before:
91
174
 
92
175
  Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
93
- => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
176
+ # => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
94
177
 
95
178
  After:
96
179
 
97
180
  Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
98
- => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
181
+ # => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
99
182
 
100
183
  Fixes #28521.
101
184
 
102
185
  *Sayan Chakraborty*
103
186
 
104
- * Fix numericality equality validation of `BigDecimal` and `Float`
105
- by casting to `BigDecimal` on both ends of the validation.
106
-
107
- *Gannon McGibbon*
108
-
187
+ * Fix year value when casting a multiparameter time hash.
109
188
 
110
- ## Rails 5.2.2.1 (March 11, 2019) ##
111
-
112
- * No changes.
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.
113
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
+ ```
114
202
 
115
- ## Rails 5.2.2 (December 04, 2018) ##
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
+ ```
116
211
 
117
- * Fix numericality validator to still use value before type cast except Active Record.
212
+ *Andrew White*
118
213
 
119
- Fixes #33651, #33686.
120
214
 
121
- *Ryuta Kamizono*
215
+ ## Rails 6.0.0.beta1 (January 18, 2019) ##
122
216
 
217
+ * Internal calls to `human_attribute_name` on an `Active Model` now pass attributes as strings instead of symbols
218
+ in some cases.
123
219
 
124
- ## Rails 5.2.1.1 (November 27, 2018) ##
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.
125
222
 
126
- * No changes.
223
+ It is recommended to cast the attribute input to your desired type as if you you are overriding that methid.
127
224
 
225
+ *Martin Larochelle*
128
226
 
129
- ## Rails 5.2.1 (August 07, 2018) ##
227
+ * Add `ActiveModel::Errors#of_kind?`.
130
228
 
131
- * No changes.
229
+ *bogdanvlviv*, *Rafael Mendonça França*
132
230
 
231
+ * Fix numericality equality validation of `BigDecimal` and `Float`
232
+ by casting to `BigDecimal` on both ends of the validation.
133
233
 
134
- ## Rails 5.2.0 (April 09, 2018) ##
234
+ *Gannon McGibbon*
135
235
 
136
- * Do not lose all multiple `:includes` with options in serialization.
236
+ * Add `#slice!` method to `ActiveModel::Errors`.
137
237
 
138
- *Mike Mangino*
238
+ *Daniel López Prat*
139
239
 
140
- * Models using the attributes API with a proc default can now be marshalled.
240
+ * Fix numericality validator to still use value before type cast except Active Record.
141
241
 
142
- Fixes #31216.
242
+ Fixes #33651, #33686.
143
243
 
144
- *Sean Griffin*
244
+ *Ryuta Kamizono*
145
245
 
146
- * Fix to working before/after validation callbacks on multiple contexts.
246
+ * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps.
147
247
 
148
- *Yoshiyuki Hirano*
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
+ ```
149
253
 
150
- * Execute `ConfirmationValidator` validation when `_confirmation`'s value is `false`.
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
+ ```
151
259
 
152
- *bogdanvlviv*
260
+ *Bogdan Gusiev*
153
261
 
154
- * Allow passing a Proc or Symbol to length validator options.
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'.
155
267
 
156
- *Matt Rohrer*
268
+ Example:
157
269
 
158
- * Add method `#merge!` for `ActiveModel::Errors`.
270
+ class User < ActiveRecord::Base
271
+ has_secure_password :recovery_password, validations: false
272
+ end
159
273
 
160
- *Jahfer Husain*
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
161
278
 
162
- * Fix regression in numericality validator when comparing Decimal and Float input
163
- values with more scale than the schema.
279
+ *Unathi Chonco*
164
280
 
165
- *Bradley Priest*
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.
166
284
 
167
- * Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
285
+ *Martin Larochelle*
168
286
 
169
- Change `#keys` to only return the keys that don't have empty messages.
287
+ * Rails 6 requires Ruby 2.5.0 or newer.
170
288
 
171
- Change `#values` to only return the not empty values.
289
+ *Jeremy Daer*, *Kasper Timm Hansen*
172
290
 
173
- Example:
174
291
 
175
- # Before
176
- person = Person.new
177
- person.errors.keys # => []
178
- person.errors.values # => []
179
- person.errors.messages # => {}
180
- person.errors[:name] # => []
181
- person.errors.messages # => {:name => []}
182
- person.errors.keys # => [:name]
183
- person.errors.values # => [[]]
184
-
185
- # After
186
- person = Person.new
187
- person.errors.keys # => []
188
- person.errors.values # => []
189
- person.errors.messages # => {}
190
- person.errors[:name] # => []
191
- person.errors.messages # => {:name => []}
192
- person.errors.keys # => []
193
- person.errors.values # => []
194
-
195
- *bogdanvlviv*
196
-
197
-
198
- Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md) for previous changes.
292
+ Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2018 David Heinemeier Hansson
1
+ Copyright (c) 2004-2019 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
@@ -5,6 +5,8 @@ They allow for Action Pack helpers to interact with non-Active Record models,
5
5
  for example. Active Model also helps with building custom ORMs for use outside of
6
6
  the Rails framework.
7
7
 
8
+ You can read more about Active Model in the {Active Model Basics}[https://edgeguides.rubyonrails.org/active_model_basics.html] guide.
9
+
8
10
  Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
9
11
  interact with Action Pack helpers, it was required to either copy chunks of
10
12
  code from Rails, or monkey patch entire helpers to make them handle objects
@@ -239,7 +241,7 @@ The latest version of Active Model can be installed with RubyGems:
239
241
 
240
242
  Source code can be downloaded as part of the Rails project on GitHub
241
243
 
242
- * https://github.com/rails/rails/tree/5-2-stable/activemodel
244
+ * https://github.com/rails/rails/tree/main/activemodel
243
245
 
244
246
 
245
247
  == License
@@ -253,7 +255,7 @@ Active Model is released under the MIT license:
253
255
 
254
256
  API documentation is at:
255
257
 
256
- * http://api.rubyonrails.org
258
+ * https://api.rubyonrails.org
257
259
 
258
260
  Bug reports for the Ruby on Rails project can be filed here:
259
261
 
@@ -261,4 +263,4 @@ Bug reports for the Ruby on Rails project can be filed here:
261
263
 
262
264
  Feature requests should be discussed on the rails-core mailing list here:
263
265
 
264
- * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
266
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -44,8 +44,7 @@ module ActiveModel
44
44
  end
45
45
  end
46
46
 
47
- protected
48
-
47
+ private
49
48
  attr_reader :user_provided_value
50
49
  end
51
50
  end
@@ -133,10 +133,6 @@ module ActiveModel
133
133
  end
134
134
 
135
135
  protected
136
-
137
- attr_reader :original_attribute
138
- alias_method :assigned?, :original_attribute
139
-
140
136
  def original_value_for_database
141
137
  if assigned?
142
138
  original_attribute.original_value_for_database
@@ -146,6 +142,9 @@ module ActiveModel
146
142
  end
147
143
 
148
144
  private
145
+ attr_reader :original_attribute
146
+ alias :assigned? :original_attribute
147
+
149
148
  def initialize_dup(other)
150
149
  if defined?(@value) && @value.duplicable?
151
150
  @value = @value.dup
@@ -27,7 +27,7 @@ module ActiveModel
27
27
  # cat.status # => 'sleeping'
28
28
  def assign_attributes(new_attributes)
29
29
  if !new_attributes.respond_to?(:stringify_keys)
30
- raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
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
 
@@ -38,7 +38,6 @@ module ActiveModel
38
38
  alias attributes= assign_attributes
39
39
 
40
40
  private
41
-
42
41
  def _assign_attributes(attributes)
43
42
  attributes.each do |k, v|
44
43
  _assign_attribute(k, v)
@@ -286,12 +286,12 @@ module ActiveModel
286
286
  method_name = matcher.method_name(attr_name)
287
287
 
288
288
  unless instance_method_already_implemented?(method_name)
289
- generate_method = "define_method_#{matcher.method_missing_target}"
289
+ generate_method = "define_method_#{matcher.target}"
290
290
 
291
291
  if respond_to?(generate_method, true)
292
292
  send(generate_method, attr_name.to_s)
293
293
  else
294
- define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
294
+ define_proxy_call true, generated_attribute_methods, method_name, matcher.target, attr_name.to_s
295
295
  end
296
296
  end
297
297
  end
@@ -352,53 +352,55 @@ module ActiveModel
352
352
 
353
353
  def attribute_method_matchers_matching(method_name)
354
354
  attribute_method_matchers_cache.compute_if_absent(method_name) do
355
- # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
356
- # will match every time.
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.
357
358
  matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
358
- matchers.map { |method| method.match(method_name) }.compact
359
+ matchers.map { |matcher| matcher.match(method_name) }.compact
359
360
  end
360
361
  end
361
362
 
362
363
  # Define a method `name` in `mod` that dispatches to `send`
363
364
  # using the given `extra` args. This falls back on `define_method`
364
365
  # and `send` if the given names cannot be compiled.
365
- def define_proxy_call(include_private, mod, name, send, *extra)
366
+ def define_proxy_call(include_private, mod, name, target, *extra)
366
367
  defn = if NAME_COMPILABLE_REGEXP.match?(name)
367
368
  "def #{name}(*args)"
368
369
  else
369
370
  "define_method(:'#{name}') do |*args|"
370
371
  end
371
372
 
372
- extra = (extra.map!(&:inspect) << "*args").join(", ".freeze)
373
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
373
374
 
374
- target = if CALL_COMPILABLE_REGEXP.match?(send)
375
- "#{"self." unless include_private}#{send}(#{extra})"
375
+ body = if CALL_COMPILABLE_REGEXP.match?(target)
376
+ "#{"self." unless include_private}#{target}(#{extra})"
376
377
  else
377
- "send(:'#{send}', #{extra})"
378
+ "send(:'#{target}', #{extra})"
378
379
  end
379
380
 
380
381
  mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
381
382
  #{defn}
382
- #{target}
383
+ #{body}
383
384
  end
385
+ ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)
384
386
  RUBY
385
387
  end
386
388
 
387
389
  class AttributeMethodMatcher #:nodoc:
388
- attr_reader :prefix, :suffix, :method_missing_target
390
+ attr_reader :prefix, :suffix, :target
389
391
 
390
- AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
392
+ AttributeMethodMatch = Struct.new(:target, :attr_name)
391
393
 
392
394
  def initialize(options = {})
393
395
  @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
394
396
  @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
395
- @method_missing_target = "#{@prefix}attribute#{@suffix}"
397
+ @target = "#{@prefix}attribute#{@suffix}"
396
398
  @method_name = "#{prefix}%s#{suffix}"
397
399
  end
398
400
 
399
401
  def match(method_name)
400
402
  if @regex =~ method_name
401
- AttributeMethodMatch.new(method_missing_target, $1, method_name)
403
+ AttributeMethodMatch.new(target, $1)
402
404
  end
403
405
  end
404
406
 
@@ -430,6 +432,7 @@ module ActiveModel
430
432
  match ? attribute_missing(match, *args, &block) : super
431
433
  end
432
434
  end
435
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
433
436
 
434
437
  # +attribute_missing+ is like +method_missing+, but for attributes. When
435
438
  # +method_missing+ is called we check to see if there is a matching
@@ -474,5 +477,43 @@ module ActiveModel
474
477
  def _read_attribute(attr)
475
478
  __send__(attr)
476
479
  end
480
+
481
+ module AttrNames # :nodoc:
482
+ DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
483
+
484
+ # We want to generate the methods via module_eval rather than
485
+ # define_method, because define_method is slower on dispatch.
486
+ # Evaluating many similar methods may use more memory as the instruction
487
+ # sequences are duplicated and cached (in MRI). define_method may
488
+ # be slower on dispatch, but if you're careful about the closure
489
+ # created, then define_method will consume much less memory.
490
+ #
491
+ # But sometimes the database might return columns with
492
+ # characters that are not allowed in normal method names (like
493
+ # 'my_column(omg)'. So to work around this we first define with
494
+ # the __temp__ identifier, and then use alias method to rename
495
+ # it to what we want.
496
+ #
497
+ # We are also defining a constant to hold the frozen string of
498
+ # the attribute name. Using a constant means that we do not have
499
+ # to allocate an object on each call to the attribute method.
500
+ # Making it frozen means that it doesn't get duped when used to
501
+ # key the @attributes in read_attribute.
502
+ def self.define_attribute_accessor_method(mod, attr_name, writer: false)
503
+ method_name = "#{attr_name}#{'=' if writer}"
504
+ if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
505
+ yield method_name, "'#{attr_name}'.freeze"
506
+ else
507
+ safe_name = attr_name.unpack1("h*")
508
+ const_name = "ATTR_#{safe_name}"
509
+ const_set(const_name, attr_name) unless const_defined?(const_name)
510
+ temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
511
+ attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
512
+ yield temp_method_name, attr_name_expr
513
+ mod.alias_method method_name, temp_method_name
514
+ mod.undef_method temp_method_name
515
+ end
516
+ end
517
+ end
477
518
  end
478
519
  end