activemodel 7.1.3.4 → 7.2.0.beta2
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 +4 -4
- data/CHANGELOG.md +15 -250
- data/README.rdoc +9 -9
- data/lib/active_model/attribute.rb +1 -1
- data/lib/active_model/attribute_assignment.rb +3 -1
- data/lib/active_model/attribute_methods.rb +30 -37
- data/lib/active_model/attribute_registration.rb +62 -22
- data/lib/active_model/attributes.rb +13 -0
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/secure_password.rb +1 -1
- data/lib/active_model/type/helpers/time_value.rb +7 -1
- data/lib/active_model/type/registry.rb +2 -3
- data/lib/active_model/validations/callbacks.rb +1 -1
- data/lib/active_model/validations/comparison.rb +1 -1
- data/lib/active_model/validations/validates.rb +1 -1
- data/lib/active_model.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcba13dc67fffbbc777417b4e5778805208184f14923bf2ac225d682c8bd8e4f
|
4
|
+
data.tar.gz: e5ca96e2d9bfe3e1c8ce506529524c79568a7bd1602bb25003c541a8ebb52f8e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3c96c9b7ee0214df39556f148840fbde844b15e2ea757a75381cb21451e270b6d2c2e16786d3b3c560878346d2d4ad43be3d48c3bd80e7cf9c1858e19b427d6e
|
7
|
+
data.tar.gz: 26dcd62bdaf43519f236efbdf8c8f44fe4f2ef9e64e68781431c559cb6095008f44824cf05d20bd24efe06c6696def2122d7ca4759d769358fe83871fd50deea
|
data/CHANGELOG.md
CHANGED
@@ -1,264 +1,29 @@
|
|
1
|
-
## Rails 7.
|
1
|
+
## Rails 7.2.0.beta2 (June 04, 2024) ##
|
2
2
|
|
3
3
|
* No changes.
|
4
4
|
|
5
5
|
|
6
|
-
## Rails 7.
|
6
|
+
## Rails 7.2.0.beta1 (May 29, 2024) ##
|
7
7
|
|
8
|
-
*
|
9
|
-
|
10
|
-
|
11
|
-
## Rails 7.1.3.2 (February 21, 2024) ##
|
12
|
-
|
13
|
-
* No changes.
|
14
|
-
|
15
|
-
|
16
|
-
## Rails 7.1.3.1 (February 21, 2024) ##
|
17
|
-
|
18
|
-
* No changes.
|
19
|
-
|
20
|
-
|
21
|
-
## Rails 7.1.3 (January 16, 2024) ##
|
22
|
-
|
23
|
-
* No changes.
|
24
|
-
|
25
|
-
|
26
|
-
## Rails 7.1.2 (November 10, 2023) ##
|
27
|
-
|
28
|
-
* Make `==(other)` method of AttributeSet safe.
|
29
|
-
|
30
|
-
*Dmitry Pogrebnoy*
|
31
|
-
|
32
|
-
|
33
|
-
## Rails 7.1.1 (October 11, 2023) ##
|
34
|
-
|
35
|
-
* No changes.
|
36
|
-
|
37
|
-
|
38
|
-
## Rails 7.1.0 (October 05, 2023) ##
|
39
|
-
|
40
|
-
* No changes.
|
41
|
-
|
42
|
-
|
43
|
-
## Rails 7.1.0.rc2 (October 01, 2023) ##
|
44
|
-
|
45
|
-
* No changes.
|
46
|
-
|
47
|
-
|
48
|
-
## Rails 7.1.0.rc1 (September 27, 2023) ##
|
49
|
-
|
50
|
-
* Remove change in the typography of user facing error messages.
|
51
|
-
For example, “can’t be blank” is again “can't be blank”.
|
52
|
-
|
53
|
-
*Rafael Mendonça França*
|
54
|
-
|
55
|
-
|
56
|
-
## Rails 7.1.0.beta1 (September 13, 2023) ##
|
57
|
-
|
58
|
-
* Support composite identifiers in `to_key`
|
59
|
-
|
60
|
-
`to_key` avoids wrapping `#id` value into an `Array` if `#id` already an array
|
61
|
-
|
62
|
-
*Nikita Vasilevsky*
|
63
|
-
|
64
|
-
* Add `ActiveModel::Conversion.param_delimiter` to configure delimiter being used in `to_param`
|
65
|
-
|
66
|
-
*Nikita Vasilevsky*
|
67
|
-
|
68
|
-
* `undefine_attribute_methods` undefines alias attribute methods along with attribute methods.
|
69
|
-
|
70
|
-
*Nikita Vasilevsky*
|
71
|
-
|
72
|
-
* Error.full_message now strips ":base" from the message.
|
73
|
-
|
74
|
-
*zzak*
|
75
|
-
|
76
|
-
* Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
|
77
|
-
`ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
|
78
|
-
|
79
|
-
*Lewis Buckley*
|
80
|
-
|
81
|
-
* Improve password length validation in ActiveModel::SecurePassword to consider byte size for BCrypt
|
82
|
-
compatibility.
|
83
|
-
|
84
|
-
The previous password length validation only considered the character count, which may not
|
85
|
-
accurately reflect the 72-byte size limit imposed by BCrypt. This change updates the validation
|
86
|
-
to consider both character count and byte size while keeping the character length validation in place.
|
87
|
-
|
88
|
-
```ruby
|
89
|
-
user = User.new(password: "a" * 73) # 73 characters
|
90
|
-
user.valid? # => false
|
91
|
-
user.errors[:password] # => ["is too long"]
|
8
|
+
* Fix a bug where type casting of string to `Time` and `DateTime` doesn't
|
9
|
+
calculate minus minute value in TZ offset correctly.
|
92
10
|
|
11
|
+
*Akira Matsuda*
|
93
12
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
```
|
13
|
+
* Port the `type_for_attribute` method to Active Model. Classes that include
|
14
|
+
`ActiveModel::Attributes` will now provide this method. This method behaves
|
15
|
+
the same for Active Model as it does for Active Record.
|
98
16
|
|
99
|
-
|
17
|
+
```ruby
|
18
|
+
class MyModel
|
19
|
+
include ActiveModel::Attributes
|
100
20
|
|
101
|
-
|
102
|
-
used to compute the password digest. The salt will change whenever the password is changed,
|
103
|
-
so it can be used to create single-use password reset tokens with `generates_token_for`:
|
104
|
-
|
105
|
-
```ruby
|
106
|
-
class User < ActiveRecord::Base
|
107
|
-
has_secure_password
|
108
|
-
|
109
|
-
generates_token_for :password_reset, expires_in: 15.minutes do
|
110
|
-
password_salt&.last(10)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
```
|
114
|
-
|
115
|
-
*Lázaro Nixon*
|
116
|
-
|
117
|
-
* Improve typography of user facing error messages. In English contractions,
|
118
|
-
the Unicode APOSTROPHE (`U+0027`) is now RIGHT SINGLE QUOTATION MARK
|
119
|
-
(`U+2019`). For example, "can't be blank" is now "can’t be blank".
|
120
|
-
|
121
|
-
*Jon Dufresne*
|
122
|
-
|
123
|
-
* Add class to `ActiveModel::MissingAttributeError` error message.
|
124
|
-
|
125
|
-
Show which class is missing the attribute in the error message:
|
126
|
-
|
127
|
-
```ruby
|
128
|
-
user = User.first
|
129
|
-
user.pets.select(:id).first.user_id
|
130
|
-
# => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
|
131
|
-
```
|
132
|
-
|
133
|
-
*Petrik de Heus*
|
134
|
-
|
135
|
-
* Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
|
136
|
-
results.
|
137
|
-
|
138
|
-
*Vasiliy Ermolovich*
|
139
|
-
|
140
|
-
* Custom attribute types that inherit from Active Model built-in types and do
|
141
|
-
not override the `serialize` method will now benefit from an optimization
|
142
|
-
when serializing attribute values for the database.
|
143
|
-
|
144
|
-
For example, with a custom type like the following:
|
145
|
-
|
146
|
-
```ruby
|
147
|
-
class DowncasedString < ActiveModel::Type::String
|
148
|
-
def cast(value)
|
149
|
-
super&.downcase
|
21
|
+
attribute :my_attribute, :integer
|
150
22
|
end
|
151
|
-
end
|
152
|
-
|
153
|
-
ActiveRecord::Type.register(:downcased_string, DowncasedString)
|
154
|
-
|
155
|
-
class User < ActiveRecord::Base
|
156
|
-
attribute :email, :downcased_string
|
157
|
-
end
|
158
|
-
|
159
|
-
user = User.new(email: "FooBar@example.com")
|
160
|
-
```
|
161
|
-
|
162
|
-
Serializing the `email` attribute for the database will be roughly twice as
|
163
|
-
fast. More expensive `cast` operations will likely see greater improvements.
|
164
|
-
|
165
|
-
*Jonathan Hefner*
|
166
|
-
|
167
|
-
* `has_secure_password` now supports password challenges via a
|
168
|
-
`password_challenge` accessor and validation.
|
169
|
-
|
170
|
-
A password challenge is a safeguard to verify that the current user is
|
171
|
-
actually the password owner. It can be used when changing sensitive model
|
172
|
-
fields, such as the password itself. It is different than a password
|
173
|
-
confirmation, which is used to prevent password typos.
|
174
|
-
|
175
|
-
When `password_challenge` is set, the validation checks that the value's
|
176
|
-
digest matches the *currently persisted* `password_digest` (i.e.
|
177
|
-
`password_digest_was`).
|
178
23
|
|
179
|
-
|
180
|
-
|
181
|
-
challenge error to be handled in the same way as other validation errors.
|
182
|
-
|
183
|
-
For example, in the controller, instead of:
|
184
|
-
|
185
|
-
```ruby
|
186
|
-
password_params = params.require(:password).permit(
|
187
|
-
:password_challenge,
|
188
|
-
:password,
|
189
|
-
:password_confirmation,
|
190
|
-
)
|
191
|
-
|
192
|
-
password_challenge = password_params.delete(:password_challenge)
|
193
|
-
@password_challenge_failed = !current_user.authenticate(password_challenge)
|
194
|
-
|
195
|
-
if !@password_challenge_failed && current_user.update(password_params)
|
196
|
-
# ...
|
197
|
-
end
|
198
|
-
```
|
199
|
-
|
200
|
-
You can now write:
|
201
|
-
|
202
|
-
```ruby
|
203
|
-
password_params = params.require(:password).permit(
|
204
|
-
:password_challenge,
|
205
|
-
:password,
|
206
|
-
:password_confirmation,
|
207
|
-
).with_defaults(password_challenge: "")
|
208
|
-
|
209
|
-
if current_user.update(password_params)
|
210
|
-
# ...
|
211
|
-
end
|
212
|
-
```
|
213
|
-
|
214
|
-
And, in the view, instead of checking `@password_challenge_failed`, you can
|
215
|
-
render an error for the `password_challenge` field just as you would for
|
216
|
-
other form fields, including utilizing `config.action_view.field_error_proc`.
|
24
|
+
MyModel.type_for_attribute(:my_attribute) # => #<ActiveModel::Type::Integer ...>
|
25
|
+
```
|
217
26
|
|
218
27
|
*Jonathan Hefner*
|
219
28
|
|
220
|
-
|
221
|
-
|
222
|
-
```ruby
|
223
|
-
validates_length_of :first_name, in: ..30
|
224
|
-
```
|
225
|
-
|
226
|
-
*fatkodima*
|
227
|
-
|
228
|
-
* Add support for beginless ranges to inclusivity/exclusivity validators:
|
229
|
-
|
230
|
-
```ruby
|
231
|
-
validates_inclusion_of :birth_date, in: -> { (..Date.today) }
|
232
|
-
```
|
233
|
-
|
234
|
-
```ruby
|
235
|
-
validates_exclusion_of :birth_date, in: -> { (..Date.today) }
|
236
|
-
```
|
237
|
-
|
238
|
-
*Bo Jeanes*
|
239
|
-
|
240
|
-
* Make validators accept lambdas without record argument
|
241
|
-
|
242
|
-
```ruby
|
243
|
-
# Before
|
244
|
-
validates_comparison_of :birth_date, less_than_or_equal_to: ->(_record) { Date.today }
|
245
|
-
|
246
|
-
# After
|
247
|
-
validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
|
248
|
-
```
|
249
|
-
|
250
|
-
*fatkodima*
|
251
|
-
|
252
|
-
* Fix casting long strings to `Date`, `Time` or `DateTime`
|
253
|
-
|
254
|
-
*fatkodima*
|
255
|
-
|
256
|
-
* Use different cache namespace for proxy calls
|
257
|
-
|
258
|
-
Models can currently have different attribute bodies for the same method
|
259
|
-
names, leading to conflicts. Adding a new namespace `:active_model_proxy`
|
260
|
-
fixes the issue.
|
261
|
-
|
262
|
-
*Chris Salzberg*
|
263
|
-
|
264
|
-
Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activemodel/CHANGELOG.md) for previous changes.
|
29
|
+
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}[
|
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}[
|
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}[
|
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}[
|
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}[
|
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}[
|
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}[
|
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}[
|
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}[
|
233
|
+
{Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validator.html]
|
234
234
|
|
235
235
|
|
236
236
|
== Download and installation
|
@@ -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: {}
|
@@ -225,28 +224,13 @@ module ActiveModel
|
|
225
224
|
method_name = pattern.method_name(new_name).to_s
|
226
225
|
target_name = pattern.method_name(old_name).to_s
|
227
226
|
parameters = pattern.parameters
|
228
|
-
mangled_name = target_name
|
229
227
|
|
230
|
-
|
231
|
-
mangled_name = "__temp__#{target_name.unpack1("h*")}"
|
232
|
-
end
|
228
|
+
mangled_name = build_mangled_name(target_name)
|
233
229
|
|
234
|
-
|
235
|
-
|
236
|
-
"self.#{target_name}(#{parameters || ''})"
|
237
|
-
else
|
238
|
-
call_args = [":'#{target_name}'"]
|
239
|
-
call_args << parameters if parameters
|
240
|
-
"send(#{call_args.join(", ")})"
|
241
|
-
end
|
230
|
+
call_args = []
|
231
|
+
call_args << parameters if parameters
|
242
232
|
|
243
|
-
|
244
|
-
|
245
|
-
batch <<
|
246
|
-
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
247
|
-
body <<
|
248
|
-
"end"
|
249
|
-
end
|
233
|
+
define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute)
|
250
234
|
end
|
251
235
|
|
252
236
|
# Is +new_name+ an alias?
|
@@ -381,6 +365,8 @@ module ActiveModel
|
|
381
365
|
super
|
382
366
|
base.class_eval do
|
383
367
|
@attribute_method_patterns_cache = nil
|
368
|
+
@aliases_by_attribute_name = nil
|
369
|
+
@generated_attribute_methods = nil
|
384
370
|
end
|
385
371
|
end
|
386
372
|
|
@@ -419,27 +405,36 @@ module ActiveModel
|
|
419
405
|
# using the given `extra` args. This falls back on `send`
|
420
406
|
# if the called name cannot be compiled.
|
421
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)
|
422
418
|
mangled_name = name
|
419
|
+
|
423
420
|
unless NAME_COMPILABLE_REGEXP.match?(name)
|
424
421
|
mangled_name = "__temp__#{name.unpack1("h*")}"
|
425
422
|
end
|
426
423
|
|
427
|
-
|
428
|
-
|
429
|
-
namespace = :"#{namespace}_#{proxy_target}_#{call_args.join("_")}}"
|
424
|
+
mangled_name
|
425
|
+
end
|
430
426
|
|
427
|
+
def define_call(code_generator, name, target_name, mangled_name, parameters, call_args, namespace:)
|
431
428
|
code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
|
432
|
-
body = if CALL_COMPILABLE_REGEXP.match?(
|
433
|
-
"self.#{
|
429
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target_name)
|
430
|
+
"self.#{target_name}(#{call_args.join(", ")})"
|
434
431
|
else
|
435
|
-
call_args.unshift(":'#{
|
432
|
+
call_args.unshift(":'#{target_name}'")
|
436
433
|
"send(#{call_args.join(", ")})"
|
437
434
|
end
|
438
435
|
|
439
|
-
modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
|
440
|
-
|
441
436
|
batch <<
|
442
|
-
"
|
437
|
+
"def #{mangled_name}(#{parameters || ''})" <<
|
443
438
|
body <<
|
444
439
|
"end"
|
445
440
|
end
|
@@ -453,7 +448,7 @@ module ActiveModel
|
|
453
448
|
def initialize(prefix: "", suffix: "", parameters: nil)
|
454
449
|
@prefix = prefix
|
455
450
|
@suffix = suffix
|
456
|
-
@parameters = parameters.nil? ?
|
451
|
+
@parameters = parameters.nil? ? "..." : parameters
|
457
452
|
@regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
|
458
453
|
@proxy_target = "#{@prefix}attribute#{@suffix}"
|
459
454
|
@method_name = "#{prefix}%s#{suffix}"
|
@@ -481,24 +476,22 @@ module ActiveModel
|
|
481
476
|
# It's also possible to instantiate related objects, so a <tt>Client</tt>
|
482
477
|
# class belonging to the +clients+ table with a +master_id+ foreign key
|
483
478
|
# can instantiate master through <tt>Client#master</tt>.
|
484
|
-
def method_missing(method,
|
479
|
+
def method_missing(method, ...)
|
485
480
|
if respond_to_without_attributes?(method, true)
|
486
481
|
super
|
487
482
|
else
|
488
|
-
match = matched_attribute_method(method.
|
489
|
-
match ? attribute_missing(match,
|
483
|
+
match = matched_attribute_method(method.name)
|
484
|
+
match ? attribute_missing(match, ...) : super
|
490
485
|
end
|
491
486
|
end
|
492
|
-
ruby2_keywords(:method_missing)
|
493
487
|
|
494
488
|
# +attribute_missing+ is like +method_missing+, but for attributes. When
|
495
489
|
# +method_missing+ is called we check to see if there is a matching
|
496
490
|
# attribute method. If so, we tell +attribute_missing+ to dispatch the
|
497
491
|
# attribute. This method can be overloaded to customize the behavior.
|
498
|
-
def attribute_missing(match,
|
499
|
-
__send__(match.proxy_target, match.attr_name,
|
492
|
+
def attribute_missing(match, ...)
|
493
|
+
__send__(match.proxy_target, match.attr_name, ...)
|
500
494
|
end
|
501
|
-
ruby2_keywords(:attribute_missing)
|
502
495
|
|
503
496
|
# A +Person+ instance with a +name+ attribute can ask
|
504
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
|
-
|
16
|
-
|
17
|
-
|
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 ||=
|
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
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
49
|
-
|
77
|
+
def pending_attribute_modifications
|
78
|
+
@pending_attribute_modifications ||= []
|
79
|
+
end
|
50
80
|
|
51
|
-
|
52
|
-
|
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
|
-
|
86
|
+
pending_attribute_modifications.each do |modification|
|
87
|
+
modification.apply_to(attribute_set)
|
88
|
+
end
|
56
89
|
end
|
57
90
|
|
58
|
-
def
|
59
|
-
|
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,6 +75,19 @@ 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
92
|
def define_method_attribute=(name, owner:)
|
80
93
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
@@ -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:
|
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:
|
@@ -94,7 +94,13 @@ module ActiveModel
|
|
94
94
|
end
|
95
95
|
|
96
96
|
if $8
|
97
|
-
offset =
|
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,
|
23
|
+
def lookup(symbol, ...)
|
24
24
|
registration = registrations[symbol]
|
25
25
|
|
26
26
|
if registration
|
27
|
-
registration.call(symbol,
|
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
|
@@ -10,7 +10,7 @@ module ActiveModel
|
|
10
10
|
include ResolveValue
|
11
11
|
|
12
12
|
def check_validity!
|
13
|
-
unless
|
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
|
13
|
+
# Examples of using the default Rails validators:
|
14
14
|
#
|
15
15
|
# validates :username, absence: true
|
16
16
|
# validates :terms, acceptance: true
|
data/lib/active_model.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activemodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.
|
4
|
+
version: 7.2.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 7.
|
19
|
+
version: 7.2.0.beta2
|
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.
|
26
|
+
version: 7.2.0.beta2
|
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,10 +112,10 @@ 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.
|
116
|
-
documentation_uri: https://api.rubyonrails.org/v7.
|
115
|
+
changelog_uri: https://github.com/rails/rails/blob/v7.2.0.beta2/activemodel/CHANGELOG.md
|
116
|
+
documentation_uri: https://api.rubyonrails.org/v7.2.0.beta2/
|
117
117
|
mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
|
118
|
-
source_code_uri: https://github.com/rails/rails/tree/v7.
|
118
|
+
source_code_uri: https://github.com/rails/rails/tree/v7.2.0.beta2/activemodel
|
119
119
|
rubygems_mfa_required: 'true'
|
120
120
|
post_install_message:
|
121
121
|
rdoc_options: []
|
@@ -125,12 +125,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
125
|
requirements:
|
126
126
|
- - ">="
|
127
127
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
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
|
-
version:
|
133
|
+
version: 1.3.1
|
134
134
|
requirements: []
|
135
135
|
rubygems_version: 3.3.27
|
136
136
|
signing_key:
|