activemodel 7.1.5 → 7.2.0.beta1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47edf9505c3471e453e228f0f5df53f81f4ef35f124245e2fa3fe21a4fe98e83
4
- data.tar.gz: 4c693d298afbfed8f0cd722d33f2426e0f3edb7305bb581e0c4ee2a3fa1203b1
3
+ metadata.gz: 70a0c28c415bc15bb939ec838af49318530a90d8f42546153450f4cc108c18c8
4
+ data.tar.gz: 57a6406ae772d62fb5b90e3df60de2ecf3f64e3a8c810cea7f24694ad70d16ef
5
5
  SHA512:
6
- metadata.gz: 59b03357543036b2856fdec81f20ae784e4af91a5229caa9651ca689e1d62b85a5a1cb27a1767ce7865535344f986c7c2759f3efbf31a0665e13da5b53831dcd
7
- data.tar.gz: 1a1a03cf6c4ed8f6d3f6462a9037a6d56343accb8c1aaf39e4320f32b359fe2f14339b498b67e2a59c1be1f796bcbee79ad36641bbefac22542f10597d8b2751
6
+ metadata.gz: 54191c069916b9400da52e84d6ba6d874570fc708a89069dd702d59db8fd37f143bc3aa0021d286d6ef9b970cbcfcf828e3111cb9b8c5f7c0502f84a88929a39
7
+ data.tar.gz: 16565d50c780f4a2ca419f3e8fff5a2856db38340ae93dc1ad0412bd0dcdc6fbd44b6559b00150ed083891fc392aac204e273f6511819aa7f12b076172f9178c
data/CHANGELOG.md CHANGED
@@ -1,301 +1,24 @@
1
- ## Rails 7.1.5 (October 30, 2024) ##
1
+ ## Rails 7.2.0.beta1 (May 29, 2024) ##
2
2
 
3
- * Fix regression in `alias_attribute` to work with user defined methods.
3
+ * Fix a bug where type casting of string to `Time` and `DateTime` doesn't
4
+ calculate minus minute value in TZ offset correctly.
4
5
 
5
- `alias_attribute` would wrongly assume the attribute accessor was generated by Active Model.
6
+ *Akira Matsuda*
6
7
 
7
- ```ruby
8
- class Person
9
- include ActiveModel::AttributeMethods
8
+ * Port the `type_for_attribute` method to Active Model. Classes that include
9
+ `ActiveModel::Attributes` will now provide this method. This method behaves
10
+ the same for Active Model as it does for Active Record.
10
11
 
11
- define_attribute_methods :name
12
- attr_accessor :name
12
+ ```ruby
13
+ class MyModel
14
+ include ActiveModel::Attributes
13
15
 
14
- alias_attribute :full_name, :name
15
- end
16
-
17
- person.full_name # => NoMethodError: undefined method `attribute' for an instance of Person
18
- ```
19
-
20
- *Jean Boussier*
21
-
22
-
23
- ## Rails 7.1.4.2 (October 23, 2024) ##
24
-
25
- * No changes.
26
-
27
-
28
- ## Rails 7.1.4.1 (October 15, 2024) ##
29
-
30
- * No changes.
31
-
32
-
33
- ## Rails 7.1.4 (August 22, 2024) ##
34
-
35
- * No changes.
36
-
37
-
38
- ## Rails 7.1.3.4 (June 04, 2024) ##
39
-
40
- * No changes.
41
-
42
-
43
- ## Rails 7.1.3.3 (May 16, 2024) ##
44
-
45
- * No changes.
46
-
47
-
48
- ## Rails 7.1.3.2 (February 21, 2024) ##
49
-
50
- * No changes.
51
-
52
-
53
- ## Rails 7.1.3.1 (February 21, 2024) ##
54
-
55
- * No changes.
56
-
57
-
58
- ## Rails 7.1.3 (January 16, 2024) ##
59
-
60
- * No changes.
61
-
62
-
63
- ## Rails 7.1.2 (November 10, 2023) ##
64
-
65
- * Make `==(other)` method of AttributeSet safe.
66
-
67
- *Dmitry Pogrebnoy*
68
-
69
-
70
- ## Rails 7.1.1 (October 11, 2023) ##
71
-
72
- * No changes.
73
-
74
-
75
- ## Rails 7.1.0 (October 05, 2023) ##
76
-
77
- * No changes.
78
-
79
-
80
- ## Rails 7.1.0.rc2 (October 01, 2023) ##
81
-
82
- * No changes.
83
-
84
-
85
- ## Rails 7.1.0.rc1 (September 27, 2023) ##
86
-
87
- * Remove change in the typography of user facing error messages.
88
- For example, “can’t be blank” is again “can't be blank”.
89
-
90
- *Rafael Mendonça França*
91
-
92
-
93
- ## Rails 7.1.0.beta1 (September 13, 2023) ##
94
-
95
- * Support composite identifiers in `to_key`
96
-
97
- `to_key` avoids wrapping `#id` value into an `Array` if `#id` already an array
98
-
99
- *Nikita Vasilevsky*
100
-
101
- * Add `ActiveModel::Conversion.param_delimiter` to configure delimiter being used in `to_param`
102
-
103
- *Nikita Vasilevsky*
104
-
105
- * `undefine_attribute_methods` undefines alias attribute methods along with attribute methods.
106
-
107
- *Nikita Vasilevsky*
108
-
109
- * Error.full_message now strips ":base" from the message.
110
-
111
- *zzak*
112
-
113
- * Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
114
- `ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
115
-
116
- *Lewis Buckley*
117
-
118
- * Improve password length validation in ActiveModel::SecurePassword to consider byte size for BCrypt
119
- compatibility.
120
-
121
- The previous password length validation only considered the character count, which may not
122
- accurately reflect the 72-byte size limit imposed by BCrypt. This change updates the validation
123
- to consider both character count and byte size while keeping the character length validation in place.
124
-
125
- ```ruby
126
- user = User.new(password: "a" * 73) # 73 characters
127
- user.valid? # => false
128
- user.errors[:password] # => ["is too long"]
129
-
130
-
131
- user = User.new(password: "あ" * 25) # 25 characters, 75 bytes
132
- user.valid? # => false
133
- user.errors[:password] # => ["is too long"]
134
- ```
135
-
136
- *ChatGPT*, *Guillermo Iguaran*
137
-
138
- * `has_secure_password` now generates an `#{attribute}_salt` method that returns the salt
139
- used to compute the password digest. The salt will change whenever the password is changed,
140
- so it can be used to create single-use password reset tokens with `generates_token_for`:
141
-
142
- ```ruby
143
- class User < ActiveRecord::Base
144
- has_secure_password
145
-
146
- generates_token_for :password_reset, expires_in: 15.minutes do
147
- password_salt&.last(10)
148
- end
149
- end
150
- ```
151
-
152
- *Lázaro Nixon*
153
-
154
- * Improve typography of user facing error messages. In English contractions,
155
- the Unicode APOSTROPHE (`U+0027`) is now RIGHT SINGLE QUOTATION MARK
156
- (`U+2019`). For example, "can't be blank" is now "can’t be blank".
157
-
158
- *Jon Dufresne*
159
-
160
- * Add class to `ActiveModel::MissingAttributeError` error message.
161
-
162
- Show which class is missing the attribute in the error message:
163
-
164
- ```ruby
165
- user = User.first
166
- user.pets.select(:id).first.user_id
167
- # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
168
- ```
169
-
170
- *Petrik de Heus*
171
-
172
- * Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
173
- results.
174
-
175
- *Vasiliy Ermolovich*
176
-
177
- * Custom attribute types that inherit from Active Model built-in types and do
178
- not override the `serialize` method will now benefit from an optimization
179
- when serializing attribute values for the database.
180
-
181
- For example, with a custom type like the following:
182
-
183
- ```ruby
184
- class DowncasedString < ActiveModel::Type::String
185
- def cast(value)
186
- super&.downcase
16
+ attribute :my_attribute, :integer
187
17
  end
188
- end
189
-
190
- ActiveRecord::Type.register(:downcased_string, DowncasedString)
191
-
192
- class User < ActiveRecord::Base
193
- attribute :email, :downcased_string
194
- end
195
-
196
- user = User.new(email: "FooBar@example.com")
197
- ```
198
18
 
199
- Serializing the `email` attribute for the database will be roughly twice as
200
- fast. More expensive `cast` operations will likely see greater improvements.
19
+ MyModel.type_for_attribute(:my_attribute) # => #<ActiveModel::Type::Integer ...>
20
+ ```
201
21
 
202
22
  *Jonathan Hefner*
203
23
 
204
- * `has_secure_password` now supports password challenges via a
205
- `password_challenge` accessor and validation.
206
-
207
- A password challenge is a safeguard to verify that the current user is
208
- actually the password owner. It can be used when changing sensitive model
209
- fields, such as the password itself. It is different than a password
210
- confirmation, which is used to prevent password typos.
211
-
212
- When `password_challenge` is set, the validation checks that the value's
213
- digest matches the *currently persisted* `password_digest` (i.e.
214
- `password_digest_was`).
215
-
216
- This allows a password challenge to be done as part of a typical `update`
217
- call, just like a password confirmation. It also allows a password
218
- challenge error to be handled in the same way as other validation errors.
219
-
220
- For example, in the controller, instead of:
221
-
222
- ```ruby
223
- password_params = params.require(:password).permit(
224
- :password_challenge,
225
- :password,
226
- :password_confirmation,
227
- )
228
-
229
- password_challenge = password_params.delete(:password_challenge)
230
- @password_challenge_failed = !current_user.authenticate(password_challenge)
231
-
232
- if !@password_challenge_failed && current_user.update(password_params)
233
- # ...
234
- end
235
- ```
236
-
237
- You can now write:
238
-
239
- ```ruby
240
- password_params = params.require(:password).permit(
241
- :password_challenge,
242
- :password,
243
- :password_confirmation,
244
- ).with_defaults(password_challenge: "")
245
-
246
- if current_user.update(password_params)
247
- # ...
248
- end
249
- ```
250
-
251
- And, in the view, instead of checking `@password_challenge_failed`, you can
252
- render an error for the `password_challenge` field just as you would for
253
- other form fields, including utilizing `config.action_view.field_error_proc`.
254
-
255
- *Jonathan Hefner*
256
-
257
- * Support infinite ranges for `LengthValidator`s `:in`/`:within` options
258
-
259
- ```ruby
260
- validates_length_of :first_name, in: ..30
261
- ```
262
-
263
- *fatkodima*
264
-
265
- * Add support for beginless ranges to inclusivity/exclusivity validators:
266
-
267
- ```ruby
268
- validates_inclusion_of :birth_date, in: -> { (..Date.today) }
269
- ```
270
-
271
- ```ruby
272
- validates_exclusion_of :birth_date, in: -> { (..Date.today) }
273
- ```
274
-
275
- *Bo Jeanes*
276
-
277
- * Make validators accept lambdas without record argument
278
-
279
- ```ruby
280
- # Before
281
- validates_comparison_of :birth_date, less_than_or_equal_to: ->(_record) { Date.today }
282
-
283
- # After
284
- validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
285
- ```
286
-
287
- *fatkodima*
288
-
289
- * Fix casting long strings to `Date`, `Time` or `DateTime`
290
-
291
- *fatkodima*
292
-
293
- * Use different cache namespace for proxy calls
294
-
295
- Models can currently have different attribute bodies for the same method
296
- names, leading to conflicts. Adding a new namespace `:active_model_proxy`
297
- fixes the issue.
298
-
299
- *Chris Salzberg*
300
-
301
- Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activemodel/CHANGELOG.md) for previous changes.
24
+ Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activemodel/CHANGELOG.md) for previous changes.
data/README.rdoc CHANGED
@@ -56,7 +56,7 @@ behavior out of the box:
56
56
  person.clear_name
57
57
  person.clear_age
58
58
 
59
- {Learn more}[link:classes/ActiveModel/AttributeMethods.html]
59
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods.html]
60
60
 
61
61
  * Callbacks for certain operations
62
62
 
@@ -74,7 +74,7 @@ behavior out of the box:
74
74
  This generates +before_create+, +around_create+ and +after_create+
75
75
  class methods that wrap your create method.
76
76
 
77
- {Learn more}[link:classes/ActiveModel/Callbacks.html]
77
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Callbacks.html]
78
78
 
79
79
  * Tracking value changes
80
80
 
@@ -110,7 +110,7 @@ behavior out of the box:
110
110
  person.save
111
111
  person.previous_changes # => {'name' => ['bob, 'robert']}
112
112
 
113
- {Learn more}[link:classes/ActiveModel/Dirty.html]
113
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Dirty.html]
114
114
 
115
115
  * Adding +errors+ interface to objects
116
116
 
@@ -141,7 +141,7 @@ behavior out of the box:
141
141
  person.errors.full_messages
142
142
  # => ["Name cannot be nil"]
143
143
 
144
- {Learn more}[link:classes/ActiveModel/Errors.html]
144
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Errors.html]
145
145
 
146
146
  * Model name introspection
147
147
 
@@ -152,7 +152,7 @@ behavior out of the box:
152
152
  NamedPerson.model_name.name # => "NamedPerson"
153
153
  NamedPerson.model_name.human # => "Named person"
154
154
 
155
- {Learn more}[link:classes/ActiveModel/Naming.html]
155
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Naming.html]
156
156
 
157
157
  * Making objects serializable
158
158
 
@@ -179,7 +179,7 @@ behavior out of the box:
179
179
  s = SerialPerson.new
180
180
  s.to_json # => "{\"name\":null}"
181
181
 
182
- {Learn more}[link:classes/ActiveModel/Serialization.html]
182
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Serialization.html]
183
183
 
184
184
  * Internationalization (i18n) support
185
185
 
@@ -190,7 +190,7 @@ behavior out of the box:
190
190
  Person.human_attribute_name('my_attribute')
191
191
  # => "My attribute"
192
192
 
193
- {Learn more}[link:classes/ActiveModel/Translation.html]
193
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Translation.html]
194
194
 
195
195
  * Validation support
196
196
 
@@ -208,7 +208,7 @@ behavior out of the box:
208
208
  person.first_name = 'zoolander'
209
209
  person.valid? # => false
210
210
 
211
- {Learn more}[link:classes/ActiveModel/Validations.html]
211
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validations.html]
212
212
 
213
213
  * Custom validators
214
214
 
@@ -230,7 +230,7 @@ behavior out of the box:
230
230
  p.name = "Bob"
231
231
  p.valid? # => true
232
232
 
233
- {Learn more}[link:classes/ActiveModel/Validator.html]
233
+ {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validator.html]
234
234
 
235
235
 
236
236
  == Download and installation
@@ -153,7 +153,7 @@ module ActiveModel
153
153
  alias :assigned? :original_attribute
154
154
 
155
155
  def initialize_dup(other)
156
- if defined?(@value) && @value.duplicable?
156
+ if @value&.duplicable?
157
157
  @value = @value.dup
158
158
  end
159
159
  end
@@ -45,8 +45,10 @@ module ActiveModel
45
45
 
46
46
  def _assign_attribute(k, v)
47
47
  setter = :"#{k}="
48
+ public_send(setter, v)
49
+ rescue NoMethodError
48
50
  if respond_to?(setter)
49
- public_send(setter, v)
51
+ raise
50
52
  else
51
53
  raise UnknownAttributeError.new(self, k.to_s)
52
54
  end
@@ -66,7 +66,6 @@ module ActiveModel
66
66
 
67
67
  NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
68
68
  CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
69
- FORWARD_PARAMETERS = "*args"
70
69
 
71
70
  included do
72
71
  class_attribute :attribute_aliases, instance_writer: false, default: {}
@@ -215,12 +214,9 @@ module ActiveModel
215
214
  end
216
215
  end
217
216
 
218
- def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
219
- ActiveSupport::CodeGenerator.batch(code_generator, __FILE__, __LINE__) do |owner|
220
- attribute_method_patterns.each do |pattern|
221
- alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
222
- end
223
- attribute_method_patterns_cache.clear
217
+ def generate_alias_attribute_methods(code_generator, new_name, old_name)
218
+ attribute_method_patterns.each do |pattern|
219
+ alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
224
220
  end
225
221
  end
226
222
 
@@ -228,28 +224,13 @@ module ActiveModel
228
224
  method_name = pattern.method_name(new_name).to_s
229
225
  target_name = pattern.method_name(old_name).to_s
230
226
  parameters = pattern.parameters
231
- mangled_name = target_name
232
227
 
233
- unless NAME_COMPILABLE_REGEXP.match?(target_name)
234
- mangled_name = "__temp__#{target_name.unpack1("h*")}"
235
- end
236
-
237
- code_generator.define_cached_method(mangled_name, as: method_name, namespace: :alias_attribute) do |batch|
238
- body = if CALL_COMPILABLE_REGEXP.match?(target_name)
239
- "self.#{target_name}(#{parameters || ''})"
240
- else
241
- call_args = [":'#{target_name}'"]
242
- call_args << parameters if parameters
243
- "send(#{call_args.join(", ")})"
244
- end
228
+ mangled_name = build_mangled_name(target_name)
245
229
 
246
- modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
230
+ call_args = []
231
+ call_args << parameters if parameters
247
232
 
248
- batch <<
249
- "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
250
- body <<
251
- "end"
252
- end
233
+ define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute)
253
234
  end
254
235
 
255
236
  # Is +new_name+ an alias?
@@ -324,41 +305,22 @@ module ActiveModel
324
305
  # person.name = 'Bob'
325
306
  # person.name # => "Bob"
326
307
  # person.name_short? # => true
327
- def define_attribute_method(attr_name, _owner: generated_attribute_methods, as: attr_name)
308
+ def define_attribute_method(attr_name, _owner: generated_attribute_methods)
328
309
  ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
329
310
  attribute_method_patterns.each do |pattern|
330
- define_attribute_method_pattern(pattern, attr_name, owner: owner, as: as)
331
- end
332
- attribute_method_patterns_cache.clear
333
- end
334
- end
311
+ method_name = pattern.method_name(attr_name)
335
312
 
336
- def define_attribute_method_pattern(pattern, attr_name, owner:, as:, override: false) # :nodoc:
337
- canonical_method_name = pattern.method_name(attr_name)
338
- public_method_name = pattern.method_name(as)
339
-
340
- # If defining a regular attribute method, we don't override methods that are explictly
341
- # defined in parrent classes.
342
- if instance_method_already_implemented?(public_method_name)
343
- # However, for `alias_attribute`, we always define the method.
344
- # We check for override second because `instance_method_already_implemented?`
345
- # also check for dangerous methods.
346
- return unless override
347
- end
313
+ unless instance_method_already_implemented?(method_name)
314
+ generate_method = "define_method_#{pattern.proxy_target}"
348
315
 
349
- generate_method = "define_method_#{pattern.proxy_target}"
350
- if respond_to?(generate_method, true)
351
- send(generate_method, attr_name.to_s, owner: owner, as: as)
352
- else
353
- define_proxy_call(
354
- owner,
355
- canonical_method_name,
356
- pattern.proxy_target,
357
- pattern.parameters,
358
- attr_name.to_s,
359
- namespace: :active_model_proxy,
360
- as: public_method_name,
361
- )
316
+ if respond_to?(generate_method, true)
317
+ send(generate_method, attr_name.to_s, owner: owner)
318
+ else
319
+ define_proxy_call(owner, method_name, pattern.proxy_target, pattern.parameters, attr_name.to_s, namespace: :active_model_proxy)
320
+ end
321
+ end
322
+ end
323
+ attribute_method_patterns_cache.clear
362
324
  end
363
325
  end
364
326
 
@@ -403,6 +365,8 @@ module ActiveModel
403
365
  super
404
366
  base.class_eval do
405
367
  @attribute_method_patterns_cache = nil
368
+ @aliases_by_attribute_name = nil
369
+ @generated_attribute_methods = nil
406
370
  end
407
371
  end
408
372
 
@@ -440,28 +404,37 @@ module ActiveModel
440
404
  # Define a method `name` in `mod` that dispatches to `send`
441
405
  # using the given `extra` args. This falls back on `send`
442
406
  # if the called name cannot be compiled.
443
- def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:, as: name)
407
+ def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:)
408
+ mangled_name = build_mangled_name(name)
409
+
410
+ call_args.map!(&:inspect)
411
+ call_args << parameters if parameters
412
+ namespace = :"#{namespace}_#{proxy_target}_#{call_args.join("_")}}"
413
+
414
+ define_call(code_generator, name, proxy_target, mangled_name, parameters, call_args, namespace: namespace)
415
+ end
416
+
417
+ def build_mangled_name(name)
444
418
  mangled_name = name
419
+
445
420
  unless NAME_COMPILABLE_REGEXP.match?(name)
446
421
  mangled_name = "__temp__#{name.unpack1("h*")}"
447
422
  end
448
423
 
449
- call_args.map!(&:inspect)
450
- call_args << parameters if parameters
451
- namespace = :"#{namespace}_#{proxy_target}"
424
+ mangled_name
425
+ end
452
426
 
453
- code_generator.define_cached_method(mangled_name, as: as, namespace: namespace) do |batch|
454
- body = if CALL_COMPILABLE_REGEXP.match?(proxy_target)
455
- "self.#{proxy_target}(#{call_args.join(", ")})"
427
+ def define_call(code_generator, name, target_name, mangled_name, parameters, call_args, namespace:)
428
+ code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
429
+ body = if CALL_COMPILABLE_REGEXP.match?(target_name)
430
+ "self.#{target_name}(#{call_args.join(", ")})"
456
431
  else
457
- call_args.unshift(":'#{proxy_target}'")
432
+ call_args.unshift(":'#{target_name}'")
458
433
  "send(#{call_args.join(", ")})"
459
434
  end
460
435
 
461
- modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
462
-
463
436
  batch <<
464
- "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
437
+ "def #{mangled_name}(#{parameters || ''})" <<
465
438
  body <<
466
439
  "end"
467
440
  end
@@ -475,7 +448,7 @@ module ActiveModel
475
448
  def initialize(prefix: "", suffix: "", parameters: nil)
476
449
  @prefix = prefix
477
450
  @suffix = suffix
478
- @parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
451
+ @parameters = parameters.nil? ? "..." : parameters
479
452
  @regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
480
453
  @proxy_target = "#{@prefix}attribute#{@suffix}"
481
454
  @method_name = "#{prefix}%s#{suffix}"
@@ -503,24 +476,22 @@ module ActiveModel
503
476
  # It's also possible to instantiate related objects, so a <tt>Client</tt>
504
477
  # class belonging to the +clients+ table with a +master_id+ foreign key
505
478
  # can instantiate master through <tt>Client#master</tt>.
506
- def method_missing(method, *args, &block)
479
+ def method_missing(method, ...)
507
480
  if respond_to_without_attributes?(method, true)
508
481
  super
509
482
  else
510
- match = matched_attribute_method(method.to_s)
511
- match ? attribute_missing(match, *args, &block) : super
483
+ match = matched_attribute_method(method.name)
484
+ match ? attribute_missing(match, ...) : super
512
485
  end
513
486
  end
514
- ruby2_keywords(:method_missing)
515
487
 
516
488
  # +attribute_missing+ is like +method_missing+, but for attributes. When
517
489
  # +method_missing+ is called we check to see if there is a matching
518
490
  # attribute method. If so, we tell +attribute_missing+ to dispatch the
519
491
  # attribute. This method can be overloaded to customize the behavior.
520
- def attribute_missing(match, *args, &block)
521
- __send__(match.proxy_target, match.attr_name, *args, &block)
492
+ def attribute_missing(match, ...)
493
+ __send__(match.proxy_target, match.attr_name, ...)
522
494
  end
523
- ruby2_keywords(:attribute_missing)
524
495
 
525
496
  # A +Person+ instance with a +name+ attribute can ask
526
497
  # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
@@ -10,17 +10,28 @@ module ActiveModel
10
10
 
11
11
  module ClassMethods # :nodoc:
12
12
  def attribute(name, type = nil, default: (no_default = true), **options)
13
+ name = resolve_attribute_name(name)
13
14
  type = resolve_type_name(type, **options) if type.is_a?(Symbol)
15
+ type = hook_attribute_type(name, type) if type
14
16
 
15
- pending = pending_attribute(name)
16
- pending.type = type if type
17
- pending.default = default unless no_default
17
+ pending_attribute_modifications << PendingType.new(name, type) if type || no_default
18
+ pending_attribute_modifications << PendingDefault.new(name, default) unless no_default
19
+
20
+ reset_default_attributes
21
+ end
22
+
23
+ def decorate_attributes(names = nil, &decorator) # :nodoc:
24
+ names = names&.map { |name| resolve_attribute_name(name) }
25
+
26
+ pending_attribute_modifications << PendingDecorator.new(names, decorator)
18
27
 
19
28
  reset_default_attributes
20
29
  end
21
30
 
22
31
  def _default_attributes # :nodoc:
23
- @default_attributes ||= build_default_attributes
32
+ @default_attributes ||= AttributeSet.new({}).tap do |attribute_set|
33
+ apply_pending_attribute_modifications(attribute_set)
34
+ end
24
35
  end
25
36
 
26
37
  def attribute_types # :nodoc:
@@ -29,40 +40,62 @@ module ActiveModel
29
40
  end
30
41
  end
31
42
 
43
+ def type_for_attribute(attribute_name, &block)
44
+ attribute_name = resolve_attribute_name(attribute_name)
45
+
46
+ if block
47
+ attribute_types.fetch(attribute_name, &block)
48
+ else
49
+ attribute_types[attribute_name]
50
+ end
51
+ end
52
+
32
53
  private
33
- class PendingAttribute # :nodoc:
34
- attr_accessor :type, :default
54
+ PendingType = Struct.new(:name, :type) do # :nodoc:
55
+ def apply_to(attribute_set)
56
+ attribute = attribute_set[name]
57
+ attribute_set[name] = attribute.with_type(type || attribute.type)
58
+ end
59
+ end
35
60
 
36
- def apply_to(attribute)
37
- attribute = attribute.with_type(type || attribute.type)
38
- attribute = attribute.with_user_default(default) if defined?(@default)
39
- attribute
61
+ PendingDefault = Struct.new(:name, :default) do # :nodoc:
62
+ def apply_to(attribute_set)
63
+ attribute_set[name] = attribute_set[name].with_user_default(default)
40
64
  end
41
65
  end
42
66
 
43
- def pending_attribute(name)
44
- @pending_attributes ||= {}
45
- @pending_attributes[resolve_attribute_name(name)] ||= PendingAttribute.new
67
+ PendingDecorator = Struct.new(:names, :decorator) do # :nodoc:
68
+ def apply_to(attribute_set)
69
+ (names || attribute_set.keys).each do |name|
70
+ attribute = attribute_set[name]
71
+ type = decorator.call(name, attribute.type)
72
+ attribute_set[name] = attribute.with_type(type) if type
73
+ end
74
+ end
46
75
  end
47
76
 
48
- def apply_pending_attributes(attribute_set)
49
- superclass.send(__method__, attribute_set) if superclass.respond_to?(__method__, true)
77
+ def pending_attribute_modifications
78
+ @pending_attribute_modifications ||= []
79
+ end
50
80
 
51
- defined?(@pending_attributes) && @pending_attributes.each do |name, pending|
52
- attribute_set[name] = pending.apply_to(attribute_set[name])
81
+ def apply_pending_attribute_modifications(attribute_set)
82
+ if superclass.respond_to?(:apply_pending_attribute_modifications, true)
83
+ superclass.send(:apply_pending_attribute_modifications, attribute_set)
53
84
  end
54
85
 
55
- attribute_set
86
+ pending_attribute_modifications.each do |modification|
87
+ modification.apply_to(attribute_set)
88
+ end
56
89
  end
57
90
 
58
- def build_default_attributes
59
- apply_pending_attributes(AttributeSet.new({}))
91
+ def reset_default_attributes
92
+ reset_default_attributes!
93
+ subclasses.each { |subclass| subclass.send(:reset_default_attributes) }
60
94
  end
61
95
 
62
- def reset_default_attributes
96
+ def reset_default_attributes!
63
97
  @default_attributes = nil
64
98
  @attribute_types = nil
65
- subclasses.each { |subclass| subclass.send(__method__) }
66
99
  end
67
100
 
68
101
  def resolve_attribute_name(name)
@@ -72,6 +105,13 @@ module ActiveModel
72
105
  def resolve_type_name(name, **options)
73
106
  Type.lookup(name, **options)
74
107
  end
108
+
109
+ # Hook for other modules to override. The attribute type is passed
110
+ # through this method immediately after it is resolved, before any type
111
+ # decorations are applied.
112
+ def hook_attribute_type(attribute, type)
113
+ type
114
+ end
75
115
  end
76
116
  end
77
117
  end
@@ -75,12 +75,25 @@ module ActiveModel
75
75
  attribute_types.keys
76
76
  end
77
77
 
78
+ ##
79
+ # :method: type_for_attribute
80
+ # :call-seq: type_for_attribute(attribute_name, &block)
81
+ #
82
+ # Returns the type of the specified attribute after applying any
83
+ # modifiers. This method is the only valid source of information for
84
+ # anything related to the types of a model's attributes. The return value
85
+ # of this method will implement the interface described by
86
+ # ActiveModel::Type::Value (though the object itself may not subclass it).
87
+ #--
88
+ # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
89
+
90
+ ##
78
91
  private
79
- def define_method_attribute=(canonical_name, owner:, as: canonical_name)
92
+ def define_method_attribute=(name, owner:)
80
93
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
81
- owner, canonical_name, writer: true,
94
+ owner, name, writer: true,
82
95
  ) do |temp_method_name, attr_name_expr|
83
- owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_model) do |batch|
96
+ owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_model) do |batch|
84
97
  batch <<
85
98
  "def #{temp_method_name}(value)" <<
86
99
  " _write_attribute(#{attr_name_expr}, value)" <<
@@ -60,7 +60,7 @@ module ActiveModel
60
60
  # Would only create the +after_create+ and +before_create+ callback methods in
61
61
  # your class.
62
62
  #
63
- # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
63
+ # NOTE: Defining the same callback multiple times will overwrite previous callback definitions.
64
64
  #
65
65
  module Callbacks
66
66
  def self.extended(base) # :nodoc:
@@ -8,9 +8,9 @@ module ActiveModel
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 7
11
- MINOR = 1
12
- TINY = 5
13
- PRE = nil
11
+ MINOR = 2
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -41,7 +41,7 @@ module ActiveModel
41
41
  #
42
42
  # To use +has_secure_password+, add bcrypt (~> 3.1.7) to your Gemfile:
43
43
  #
44
- # gem 'bcrypt', '~> 3.1.7'
44
+ # gem "bcrypt", "~> 3.1.7"
45
45
  #
46
46
  # ==== Examples
47
47
  #
@@ -94,7 +94,13 @@ module ActiveModel
94
94
  end
95
95
 
96
96
  if $8
97
- offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60
97
+ offset = \
98
+ if $8 == "Z"
99
+ 0
100
+ else
101
+ offset_h, offset_m = $8.to_i, $9.to_i
102
+ offset_h.to_i * 3600 + (offset_h.negative? ? -1 : 1) * offset_m * 60
103
+ end
98
104
  end
99
105
 
100
106
  new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
@@ -20,16 +20,15 @@ module ActiveModel
20
20
  registrations[type_name] = block
21
21
  end
22
22
 
23
- def lookup(symbol, *args)
23
+ def lookup(symbol, ...)
24
24
  registration = registrations[symbol]
25
25
 
26
26
  if registration
27
- registration.call(symbol, *args)
27
+ registration.call(symbol, ...)
28
28
  else
29
29
  raise ArgumentError, "Unknown type #{symbol.inspect}"
30
30
  end
31
31
  end
32
- ruby2_keywords(:lookup)
33
32
 
34
33
  private
35
34
  attr_reader :registrations
@@ -101,7 +101,7 @@ module ActiveModel
101
101
  options[:on] = Array(options[:on])
102
102
  options[:if] = [
103
103
  ->(o) {
104
- !(options[:on] & Array(o.validation_context)).empty?
104
+ options[:on].intersect?(Array(o.validation_context))
105
105
  },
106
106
  *options[:if]
107
107
  ]
@@ -10,7 +10,7 @@ module ActiveModel
10
10
  include ResolveValue
11
11
 
12
12
  def check_validity!
13
- unless (options.keys & COMPARE_CHECKS.keys).any?
13
+ unless options.keys.intersect?(COMPARE_CHECKS.keys)
14
14
  raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
15
15
  ":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied."
16
16
  end
@@ -10,7 +10,7 @@ module ActiveModel
10
10
  # validators can be overridden inside specific classes by creating
11
11
  # custom validator classes in their place such as PresenceValidator.
12
12
  #
13
- # Examples of using the default \Rails validators:
13
+ # Examples of using the default Rails validators:
14
14
  #
15
15
  # validates :username, absence: true
16
16
  # validates :terms, acceptance: true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activemodel
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.5
4
+ version: 7.2.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-31 00:00:00.000000000 Z
11
+ date: 2024-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 7.1.5
19
+ version: 7.2.0.beta1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 7.1.5
26
+ version: 7.2.0.beta1
27
27
  description: A toolkit for building modeling frameworks like Active Record. Rich support
28
28
  for attributes, callbacks, validations, serialization, internationalization, and
29
29
  testing.
@@ -112,12 +112,12 @@ licenses:
112
112
  - MIT
113
113
  metadata:
114
114
  bug_tracker_uri: https://github.com/rails/rails/issues
115
- changelog_uri: https://github.com/rails/rails/blob/v7.1.5/activemodel/CHANGELOG.md
116
- documentation_uri: https://api.rubyonrails.org/v7.1.5/
115
+ changelog_uri: https://github.com/rails/rails/blob/v7.2.0.beta1/activemodel/CHANGELOG.md
116
+ documentation_uri: https://api.rubyonrails.org/v7.2.0.beta1/
117
117
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
118
- source_code_uri: https://github.com/rails/rails/tree/v7.1.5/activemodel
118
+ source_code_uri: https://github.com/rails/rails/tree/v7.2.0.beta1/activemodel
119
119
  rubygems_mfa_required: 'true'
120
- post_install_message:
120
+ post_install_message:
121
121
  rdoc_options: []
122
122
  require_paths:
123
123
  - lib
@@ -125,15 +125,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
125
125
  requirements:
126
126
  - - ">="
127
127
  - !ruby/object:Gem::Version
128
- version: 2.7.0
128
+ version: 3.1.0
129
129
  required_rubygems_version: !ruby/object:Gem::Requirement
130
130
  requirements:
131
131
  - - ">="
132
132
  - !ruby/object:Gem::Version
133
133
  version: '0'
134
134
  requirements: []
135
- rubygems_version: 3.5.16
136
- signing_key:
135
+ rubygems_version: 3.5.10
136
+ signing_key:
137
137
  specification_version: 4
138
138
  summary: A toolkit for building modeling frameworks (part of Rails).
139
139
  test_files: []