activemodel 5.2.8.1 → 6.0.6.1

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +179 -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 +3 -3
  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 +13 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7f6668533dd90a9b8c479fc71940522acf40284b4604e8d689d637129e6795a
4
- data.tar.gz: 9a519747c7fc021167852457a0e03056bc68351001f61d366d32386afe584d11
3
+ metadata.gz: 186b71a33dc404ff631b27cfdd72896a9d5781d07fba7f8238f399f03e9dcc17
4
+ data.tar.gz: d0171f0cba7b4d82d9a27319e3eaf1ac06b4b580435c4792ce1f25f8ac619716
5
5
  SHA512:
6
- metadata.gz: 0f5a6ba7be9164544db261136c14337a256ad42cf9776e8364b745a6ff2c90f32f5e7a53e24ddb6032971de7e6bca59cf27808dea11d060ab5c3b20b92baef9d
7
- data.tar.gz: b63d763a8de2b5438801b3698f33522cd68f37c22de007d752a4d1d9250b6f2ed31aab780cc07c4179670c863bb517286b85dd77ffb9cdb1624cbf5cbbe3935b
6
+ metadata.gz: bbeefb791624e227b5b54f0da17c83f78194d750114e5e08a78f5ffc29d43dee7923a349b19cf56e95dc9b2e5488f04a2dc194585eed86ff58b3cdccda638ca2
7
+ data.tar.gz: c2bb1200ffb43a7dce74be1812065d867752555bd6a9dfef4ae7b8d2f4907b8abb54d992635d5b883478bdf3af19ec5bfee73ed105fc20cbdd863fa6c412387a
data/CHANGELOG.md CHANGED
@@ -1,79 +1,139 @@
1
- ## Rails 5.2.8.1 (July 12, 2022) ##
1
+ ## Rails 6.0.6.1 (January 17, 2023) ##
2
2
 
3
3
  * No changes.
4
4
 
5
5
 
6
- ## Rails 5.2.8 (May 09, 2022) ##
6
+ ## Rails 6.0.6 (September 09, 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.1 (July 12, 2022) ##
12
12
 
13
13
  * No changes.
14
14
 
15
15
 
16
- ## Rails 5.2.7 (March 10, 2022) ##
16
+ ## Rails 6.0.5 (May 09, 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.8 (April 26, 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.7 (March 08, 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.6 (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.5 (February 11, 2022) ##
37
37
 
38
38
  * No changes.
39
39
 
40
40
 
41
- ## Rails 5.2.5 (March 26, 2021) ##
41
+ ## Rails 6.0.4.4 (December 15, 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.3 (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.2 (December 14, 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.1 (August 19, 2021) ##
57
57
 
58
58
  * No changes.
59
59
 
60
60
 
61
- ## Rails 5.2.4.3 (May 18, 2020) ##
61
+ ## Rails 6.0.4 (June 15, 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.7 (May 05, 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.6 (March 26, 2021) ##
72
72
 
73
73
  * No changes.
74
74
 
75
75
 
76
- ## Rails 5.2.4 (November 27, 2019) ##
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) ##
77
137
 
78
138
  * Type cast falsy boolean symbols on boolean attribute as false.
79
139
 
@@ -81,8 +141,36 @@
81
141
 
82
142
  *Ryuta Kamizono*
83
143
 
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
84
164
 
85
- ## Rails 5.2.3 (March 27, 2019) ##
165
+ *Hugo Vacher*
166
+
167
+
168
+ ## Rails 6.0.0.beta3 (March 11, 2019) ##
169
+
170
+ * No changes.
171
+
172
+
173
+ ## Rails 6.0.0.beta2 (February 25, 2019) ##
86
174
 
87
175
  * Fix date value when casting a multiparameter date hash to not convert
88
176
  from Gregorian date to Julian date.
@@ -90,109 +178,120 @@
90
178
  Before:
91
179
 
92
180
  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>
181
+ # => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
94
182
 
95
183
  After:
96
184
 
97
185
  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>
186
+ # => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
99
187
 
100
188
  Fixes #28521.
101
189
 
102
190
  *Sayan Chakraborty*
103
191
 
104
- * Fix numericality equality validation of `BigDecimal` and `Float`
105
- by casting to `BigDecimal` on both ends of the validation.
106
-
107
- *Gannon McGibbon*
192
+ * Fix year value when casting a multiparameter time hash.
108
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.
109
198
 
110
- ## Rails 5.2.2.1 (March 11, 2019) ##
111
-
112
- * No changes.
113
-
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
+ ```
114
207
 
115
- ## Rails 5.2.2 (December 04, 2018) ##
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
+ ```
116
216
 
117
- * Fix numericality validator to still use value before type cast except Active Record.
217
+ *Andrew White*
118
218
 
119
- Fixes #33651, #33686.
120
219
 
121
- *Ryuta Kamizono*
220
+ ## Rails 6.0.0.beta1 (January 18, 2019) ##
122
221
 
222
+ * Internal calls to `human_attribute_name` on an `Active Model` now pass attributes as strings instead of symbols
223
+ in some cases.
123
224
 
124
- ## Rails 5.2.1.1 (November 27, 2018) ##
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.
125
227
 
126
- * No changes.
228
+ It is recommended to cast the attribute input to your desired type as if you you are overriding that methid.
127
229
 
230
+ *Martin Larochelle*
128
231
 
129
- ## Rails 5.2.1 (August 07, 2018) ##
232
+ * Add `ActiveModel::Errors#of_kind?`.
130
233
 
131
- * No changes.
234
+ *bogdanvlviv*, *Rafael Mendonça França*
132
235
 
236
+ * Fix numericality equality validation of `BigDecimal` and `Float`
237
+ by casting to `BigDecimal` on both ends of the validation.
133
238
 
134
- ## Rails 5.2.0 (April 09, 2018) ##
239
+ *Gannon McGibbon*
135
240
 
136
- * Do not lose all multiple `:includes` with options in serialization.
241
+ * Add `#slice!` method to `ActiveModel::Errors`.
137
242
 
138
- *Mike Mangino*
243
+ *Daniel López Prat*
139
244
 
140
- * Models using the attributes API with a proc default can now be marshalled.
245
+ * Fix numericality validator to still use value before type cast except Active Record.
141
246
 
142
- Fixes #31216.
247
+ Fixes #33651, #33686.
143
248
 
144
- *Sean Griffin*
249
+ *Ryuta Kamizono*
145
250
 
146
- * Fix to working before/after validation callbacks on multiple contexts.
251
+ * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps.
147
252
 
148
- *Yoshiyuki Hirano*
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
+ ```
149
258
 
150
- * Execute `ConfirmationValidator` validation when `_confirmation`'s value is `false`.
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
+ ```
151
264
 
152
- *bogdanvlviv*
265
+ *Bogdan Gusiev*
153
266
 
154
- * Allow passing a Proc or Symbol to length validator options.
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'.
155
272
 
156
- *Matt Rohrer*
273
+ Example:
157
274
 
158
- * Add method `#merge!` for `ActiveModel::Errors`.
275
+ class User < ActiveRecord::Base
276
+ has_secure_password :recovery_password, validations: false
277
+ end
159
278
 
160
- *Jahfer Husain*
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
161
283
 
162
- * Fix regression in numericality validator when comparing Decimal and Float input
163
- values with more scale than the schema.
284
+ *Unathi Chonco*
164
285
 
165
- *Bradley Priest*
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.
166
289
 
167
- * Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
290
+ *Martin Larochelle*
168
291
 
169
- Change `#keys` to only return the keys that don't have empty messages.
292
+ * Rails 6 requires Ruby 2.5.0 or newer.
170
293
 
171
- Change `#values` to only return the not empty values.
294
+ *Jeremy Daer*, *Kasper Timm Hansen*
172
295
 
173
- Example:
174
296
 
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.
297
+ 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