activemodel 7.1.5.2 → 8.1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -281
- data/README.rdoc +10 -10
- data/lib/active_model/attribute/user_provided_default.rb +10 -0
- data/lib/active_model/attribute.rb +10 -2
- data/lib/active_model/attribute_assignment.rb +26 -3
- data/lib/active_model/attribute_methods.rb +42 -43
- data/lib/active_model/attribute_mutation_tracker.rb +5 -5
- data/lib/active_model/attribute_registration.rb +62 -22
- data/lib/active_model/attribute_set.rb +1 -1
- data/lib/active_model/attributes/normalization.rb +192 -0
- data/lib/active_model/attributes.rb +13 -0
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +16 -9
- data/lib/active_model/error.rb +3 -2
- data/lib/active_model/errors.rb +1 -4
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +7 -3
- data/lib/active_model/model.rb +2 -2
- data/lib/active_model/naming.rb +0 -1
- data/lib/active_model/nested_error.rb +1 -3
- data/lib/active_model/railtie.rb +8 -4
- data/lib/active_model/secure_password.rb +62 -5
- data/lib/active_model/serialization.rb +21 -21
- data/lib/active_model/translation.rb +22 -5
- data/lib/active_model/type/big_integer.rb +21 -0
- data/lib/active_model/type/boolean.rb +1 -0
- data/lib/active_model/type/date.rb +1 -0
- data/lib/active_model/type/date_time.rb +8 -0
- data/lib/active_model/type/decimal.rb +1 -0
- data/lib/active_model/type/float.rb +9 -8
- data/lib/active_model/type/helpers/immutable.rb +13 -0
- data/lib/active_model/type/helpers/time_value.rb +12 -15
- data/lib/active_model/type/helpers/timezone.rb +5 -1
- data/lib/active_model/type/helpers.rb +1 -0
- data/lib/active_model/type/immutable_string.rb +2 -0
- data/lib/active_model/type/integer.rb +31 -19
- data/lib/active_model/type/registry.rb +2 -3
- data/lib/active_model/type/string.rb +4 -0
- data/lib/active_model/type/value.rb +4 -4
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +11 -1
- data/lib/active_model/validations/comparison.rb +1 -1
- data/lib/active_model/validations/validates.rb +8 -3
- data/lib/active_model/validations.rb +57 -32
- data/lib/active_model.rb +6 -0
- metadata +10 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7d64f9c0fada4afc52f06c6b1e86cea5506c3c0e5bbb62007240efaa9635f5d
|
|
4
|
+
data.tar.gz: 73f7293af65afdd7e75dc8403f0625787c6e0e911ac629e689bd47576b995192
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bebff0c62141aa5ae3aa8983b261dc35634d2f3ae2266504a7f0bfc6184558bc2860d487ae452ffab1ed548fcf84e9442eebec4682cec2ec69bde83bd672370
|
|
7
|
+
data.tar.gz: 13f68620adf0b69c15818c6e290f589e263d33cdf608bb82c50928cc63c2a20ee27fb7b8f6df543e501ecb16e10dea02a409f7759a54db3e197d59bc50e2c3ff
|
data/CHANGELOG.md
CHANGED
|
@@ -1,311 +1,51 @@
|
|
|
1
|
-
## Rails
|
|
1
|
+
## Rails 8.1.2.1 (March 23, 2026) ##
|
|
2
2
|
|
|
3
3
|
* No changes.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
## Rails
|
|
6
|
+
## Rails 8.1.2 (January 08, 2026) ##
|
|
7
7
|
|
|
8
8
|
* No changes.
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
## Rails
|
|
12
|
-
|
|
13
|
-
* Fix regression in `alias_attribute` to work with user defined methods.
|
|
14
|
-
|
|
15
|
-
`alias_attribute` would wrongly assume the attribute accessor was generated by Active Model.
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
class Person
|
|
19
|
-
include ActiveModel::AttributeMethods
|
|
20
|
-
|
|
21
|
-
define_attribute_methods :name
|
|
22
|
-
attr_accessor :name
|
|
23
|
-
|
|
24
|
-
alias_attribute :full_name, :name
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
person.full_name # => NoMethodError: undefined method `attribute' for an instance of Person
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
*Jean Boussier*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
## Rails 7.1.4.2 (October 23, 2024) ##
|
|
34
|
-
|
|
35
|
-
* No changes.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## Rails 7.1.4.1 (October 15, 2024) ##
|
|
39
|
-
|
|
40
|
-
* No changes.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
## Rails 7.1.4 (August 22, 2024) ##
|
|
11
|
+
## Rails 8.1.1 (October 28, 2025) ##
|
|
44
12
|
|
|
45
13
|
* No changes.
|
|
46
14
|
|
|
47
15
|
|
|
48
|
-
## Rails
|
|
49
|
-
|
|
50
|
-
* No changes.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
## Rails 7.1.3.3 (May 16, 2024) ##
|
|
54
|
-
|
|
55
|
-
* No changes.
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
## Rails 7.1.3.2 (February 21, 2024) ##
|
|
59
|
-
|
|
60
|
-
* No changes.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
## Rails 7.1.3.1 (February 21, 2024) ##
|
|
64
|
-
|
|
65
|
-
* No changes.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
## Rails 7.1.3 (January 16, 2024) ##
|
|
69
|
-
|
|
70
|
-
* No changes.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
## Rails 7.1.2 (November 10, 2023) ##
|
|
74
|
-
|
|
75
|
-
* Make `==(other)` method of AttributeSet safe.
|
|
76
|
-
|
|
77
|
-
*Dmitry Pogrebnoy*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
## Rails 7.1.1 (October 11, 2023) ##
|
|
81
|
-
|
|
82
|
-
* No changes.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
## Rails 7.1.0 (October 05, 2023) ##
|
|
86
|
-
|
|
87
|
-
* No changes.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
## Rails 7.1.0.rc2 (October 01, 2023) ##
|
|
91
|
-
|
|
92
|
-
* No changes.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
## Rails 7.1.0.rc1 (September 27, 2023) ##
|
|
96
|
-
|
|
97
|
-
* Remove change in the typography of user facing error messages.
|
|
98
|
-
For example, “can’t be blank” is again “can't be blank”.
|
|
99
|
-
|
|
100
|
-
*Rafael Mendonça França*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
## Rails 7.1.0.beta1 (September 13, 2023) ##
|
|
104
|
-
|
|
105
|
-
* Support composite identifiers in `to_key`
|
|
106
|
-
|
|
107
|
-
`to_key` avoids wrapping `#id` value into an `Array` if `#id` already an array
|
|
108
|
-
|
|
109
|
-
*Nikita Vasilevsky*
|
|
110
|
-
|
|
111
|
-
* Add `ActiveModel::Conversion.param_delimiter` to configure delimiter being used in `to_param`
|
|
112
|
-
|
|
113
|
-
*Nikita Vasilevsky*
|
|
114
|
-
|
|
115
|
-
* `undefine_attribute_methods` undefines alias attribute methods along with attribute methods.
|
|
116
|
-
|
|
117
|
-
*Nikita Vasilevsky*
|
|
16
|
+
## Rails 8.1.0 (October 22, 2025) ##
|
|
118
17
|
|
|
119
|
-
*
|
|
18
|
+
* Add `reset_token: { expires_in: ... }` option to `has_secure_password`.
|
|
120
19
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
* Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
|
|
124
|
-
`ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
|
|
125
|
-
|
|
126
|
-
*Lewis Buckley*
|
|
127
|
-
|
|
128
|
-
* Improve password length validation in ActiveModel::SecurePassword to consider byte size for BCrypt
|
|
129
|
-
compatibility.
|
|
130
|
-
|
|
131
|
-
The previous password length validation only considered the character count, which may not
|
|
132
|
-
accurately reflect the 72-byte size limit imposed by BCrypt. This change updates the validation
|
|
133
|
-
to consider both character count and byte size while keeping the character length validation in place.
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
user = User.new(password: "a" * 73) # 73 characters
|
|
137
|
-
user.valid? # => false
|
|
138
|
-
user.errors[:password] # => ["is too long"]
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
user = User.new(password: "あ" * 25) # 25 characters, 75 bytes
|
|
142
|
-
user.valid? # => false
|
|
143
|
-
user.errors[:password] # => ["is too long"]
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
*ChatGPT*, *Guillermo Iguaran*
|
|
147
|
-
|
|
148
|
-
* `has_secure_password` now generates an `#{attribute}_salt` method that returns the salt
|
|
149
|
-
used to compute the password digest. The salt will change whenever the password is changed,
|
|
150
|
-
so it can be used to create single-use password reset tokens with `generates_token_for`:
|
|
151
|
-
|
|
152
|
-
```ruby
|
|
153
|
-
class User < ActiveRecord::Base
|
|
154
|
-
has_secure_password
|
|
155
|
-
|
|
156
|
-
generates_token_for :password_reset, expires_in: 15.minutes do
|
|
157
|
-
password_salt&.last(10)
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
*Lázaro Nixon*
|
|
163
|
-
|
|
164
|
-
* Improve typography of user facing error messages. In English contractions,
|
|
165
|
-
the Unicode APOSTROPHE (`U+0027`) is now RIGHT SINGLE QUOTATION MARK
|
|
166
|
-
(`U+2019`). For example, "can't be blank" is now "can’t be blank".
|
|
167
|
-
|
|
168
|
-
*Jon Dufresne*
|
|
169
|
-
|
|
170
|
-
* Add class to `ActiveModel::MissingAttributeError` error message.
|
|
171
|
-
|
|
172
|
-
Show which class is missing the attribute in the error message:
|
|
20
|
+
Allows configuring the expiry duration of password reset tokens (default remains 15 minutes for backwards compatibility).
|
|
173
21
|
|
|
174
22
|
```ruby
|
|
175
|
-
|
|
176
|
-
user.pets.select(:id).first.user_id
|
|
177
|
-
# => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
|
|
23
|
+
has_secure_password reset_token: { expires_in: 1.hour }
|
|
178
24
|
```
|
|
179
25
|
|
|
180
|
-
*
|
|
181
|
-
|
|
182
|
-
* Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
|
|
183
|
-
results.
|
|
26
|
+
*Jevin Sew*, *Abeid Ahmed*
|
|
184
27
|
|
|
185
|
-
|
|
28
|
+
* Add `except_on:` option for validation callbacks.
|
|
186
29
|
|
|
187
|
-
*
|
|
188
|
-
not override the `serialize` method will now benefit from an optimization
|
|
189
|
-
when serializing attribute values for the database.
|
|
30
|
+
*Ben Sheldon*
|
|
190
31
|
|
|
191
|
-
|
|
32
|
+
* Backport `ActiveRecord::Normalization` to `ActiveModel::Attributes::Normalization`
|
|
192
33
|
|
|
193
34
|
```ruby
|
|
194
|
-
class
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
end
|
|
198
|
-
end
|
|
35
|
+
class User
|
|
36
|
+
include ActiveModel::Attributes
|
|
37
|
+
include ActiveModel::Attributes::Normalization
|
|
199
38
|
|
|
200
|
-
|
|
39
|
+
attribute :email, :string
|
|
201
40
|
|
|
202
|
-
|
|
203
|
-
attribute :email, :downcased_string
|
|
41
|
+
normalizes :email, with: -> email { email.strip.downcase }
|
|
204
42
|
end
|
|
205
43
|
|
|
206
|
-
user = User.new
|
|
44
|
+
user = User.new
|
|
45
|
+
user.email = " CRUISE-CONTROL@EXAMPLE.COM\n"
|
|
46
|
+
user.email # => "cruise-control@example.com"
|
|
207
47
|
```
|
|
208
48
|
|
|
209
|
-
|
|
210
|
-
fast. More expensive `cast` operations will likely see greater improvements.
|
|
211
|
-
|
|
212
|
-
*Jonathan Hefner*
|
|
213
|
-
|
|
214
|
-
* `has_secure_password` now supports password challenges via a
|
|
215
|
-
`password_challenge` accessor and validation.
|
|
216
|
-
|
|
217
|
-
A password challenge is a safeguard to verify that the current user is
|
|
218
|
-
actually the password owner. It can be used when changing sensitive model
|
|
219
|
-
fields, such as the password itself. It is different than a password
|
|
220
|
-
confirmation, which is used to prevent password typos.
|
|
221
|
-
|
|
222
|
-
When `password_challenge` is set, the validation checks that the value's
|
|
223
|
-
digest matches the *currently persisted* `password_digest` (i.e.
|
|
224
|
-
`password_digest_was`).
|
|
225
|
-
|
|
226
|
-
This allows a password challenge to be done as part of a typical `update`
|
|
227
|
-
call, just like a password confirmation. It also allows a password
|
|
228
|
-
challenge error to be handled in the same way as other validation errors.
|
|
229
|
-
|
|
230
|
-
For example, in the controller, instead of:
|
|
231
|
-
|
|
232
|
-
```ruby
|
|
233
|
-
password_params = params.require(:password).permit(
|
|
234
|
-
:password_challenge,
|
|
235
|
-
:password,
|
|
236
|
-
:password_confirmation,
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
password_challenge = password_params.delete(:password_challenge)
|
|
240
|
-
@password_challenge_failed = !current_user.authenticate(password_challenge)
|
|
241
|
-
|
|
242
|
-
if !@password_challenge_failed && current_user.update(password_params)
|
|
243
|
-
# ...
|
|
244
|
-
end
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
You can now write:
|
|
248
|
-
|
|
249
|
-
```ruby
|
|
250
|
-
password_params = params.require(:password).permit(
|
|
251
|
-
:password_challenge,
|
|
252
|
-
:password,
|
|
253
|
-
:password_confirmation,
|
|
254
|
-
).with_defaults(password_challenge: "")
|
|
255
|
-
|
|
256
|
-
if current_user.update(password_params)
|
|
257
|
-
# ...
|
|
258
|
-
end
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
And, in the view, instead of checking `@password_challenge_failed`, you can
|
|
262
|
-
render an error for the `password_challenge` field just as you would for
|
|
263
|
-
other form fields, including utilizing `config.action_view.field_error_proc`.
|
|
264
|
-
|
|
265
|
-
*Jonathan Hefner*
|
|
266
|
-
|
|
267
|
-
* Support infinite ranges for `LengthValidator`s `:in`/`:within` options
|
|
268
|
-
|
|
269
|
-
```ruby
|
|
270
|
-
validates_length_of :first_name, in: ..30
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
*fatkodima*
|
|
274
|
-
|
|
275
|
-
* Add support for beginless ranges to inclusivity/exclusivity validators:
|
|
276
|
-
|
|
277
|
-
```ruby
|
|
278
|
-
validates_inclusion_of :birth_date, in: -> { (..Date.today) }
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
```ruby
|
|
282
|
-
validates_exclusion_of :birth_date, in: -> { (..Date.today) }
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
*Bo Jeanes*
|
|
286
|
-
|
|
287
|
-
* Make validators accept lambdas without record argument
|
|
288
|
-
|
|
289
|
-
```ruby
|
|
290
|
-
# Before
|
|
291
|
-
validates_comparison_of :birth_date, less_than_or_equal_to: ->(_record) { Date.today }
|
|
292
|
-
|
|
293
|
-
# After
|
|
294
|
-
validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
*fatkodima*
|
|
298
|
-
|
|
299
|
-
* Fix casting long strings to `Date`, `Time` or `DateTime`
|
|
300
|
-
|
|
301
|
-
*fatkodima*
|
|
302
|
-
|
|
303
|
-
* Use different cache namespace for proxy calls
|
|
304
|
-
|
|
305
|
-
Models can currently have different attribute bodies for the same method
|
|
306
|
-
names, leading to conflicts. Adding a new namespace `:active_model_proxy`
|
|
307
|
-
fixes the issue.
|
|
308
|
-
|
|
309
|
-
*Chris Salzberg*
|
|
49
|
+
*Sean Doyle*
|
|
310
50
|
|
|
311
|
-
Please check [
|
|
51
|
+
Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-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
|
|
@@ -261,6 +261,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
|
|
|
261
261
|
|
|
262
262
|
* https://github.com/rails/rails/issues
|
|
263
263
|
|
|
264
|
-
Feature requests should be discussed on the
|
|
264
|
+
Feature requests should be discussed on the rubyonrails-core forum here:
|
|
265
265
|
|
|
266
266
|
* https://discuss.rubyonrails.org/c/rubyonrails-core
|
|
@@ -26,6 +26,16 @@ module ActiveModel
|
|
|
26
26
|
self.class.new(name, user_provided_value, type, original_attribute)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
def dup_or_share # :nodoc:
|
|
30
|
+
# Can't elide dup when the default is a Proc
|
|
31
|
+
# See Attribute#dup_or_share
|
|
32
|
+
if @user_provided_value.is_a?(Proc)
|
|
33
|
+
dup
|
|
34
|
+
else
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
29
39
|
def marshal_dump
|
|
30
40
|
result = [
|
|
31
41
|
name,
|
|
@@ -38,7 +38,7 @@ module ActiveModel
|
|
|
38
38
|
@value = value unless value.nil?
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def value
|
|
41
|
+
def value(&)
|
|
42
42
|
# `defined?` is cheaper than `||=` when we get back falsy values
|
|
43
43
|
@value = type_cast(value_before_type_cast) unless defined?(@value)
|
|
44
44
|
@value
|
|
@@ -96,6 +96,14 @@ module ActiveModel
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
def dup_or_share # :nodoc:
|
|
100
|
+
if @type.mutable?
|
|
101
|
+
dup
|
|
102
|
+
else
|
|
103
|
+
self # If the underlying type is immutable we can get away with not duping
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
99
107
|
def type_cast(*)
|
|
100
108
|
raise NotImplementedError
|
|
101
109
|
end
|
|
@@ -153,7 +161,7 @@ module ActiveModel
|
|
|
153
161
|
alias :assigned? :original_attribute
|
|
154
162
|
|
|
155
163
|
def initialize_dup(other)
|
|
156
|
-
if
|
|
164
|
+
if @value&.duplicable?
|
|
157
165
|
@value = @value.dup
|
|
158
166
|
end
|
|
159
167
|
end
|
|
@@ -36,19 +36,42 @@ module ActiveModel
|
|
|
36
36
|
|
|
37
37
|
alias attributes= assign_attributes
|
|
38
38
|
|
|
39
|
+
# Like `BasicObject#method_missing`, `#attribute_writer_missing` is invoked
|
|
40
|
+
# when `#assign_attributes` is passed an unknown attribute name.
|
|
41
|
+
#
|
|
42
|
+
# By default, `#attribute_writer_missing` raises an UnknownAttributeError.
|
|
43
|
+
#
|
|
44
|
+
# class Rectangle
|
|
45
|
+
# include ActiveModel::AttributeAssignment
|
|
46
|
+
#
|
|
47
|
+
# attr_accessor :length, :width
|
|
48
|
+
#
|
|
49
|
+
# def attribute_writer_missing(name, value)
|
|
50
|
+
# Rails.logger.warn "Tried to assign to unknown attribute #{name}"
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# rectangle = Rectangle.new
|
|
55
|
+
# rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"
|
|
56
|
+
def attribute_writer_missing(name, value)
|
|
57
|
+
raise UnknownAttributeError.new(self, name)
|
|
58
|
+
end
|
|
59
|
+
|
|
39
60
|
private
|
|
40
61
|
def _assign_attributes(attributes)
|
|
41
|
-
attributes.
|
|
62
|
+
attributes.each_pair do |k, v|
|
|
42
63
|
_assign_attribute(k, v)
|
|
43
64
|
end
|
|
44
65
|
end
|
|
45
66
|
|
|
46
67
|
def _assign_attribute(k, v)
|
|
47
68
|
setter = :"#{k}="
|
|
69
|
+
public_send(setter, v)
|
|
70
|
+
rescue NoMethodError
|
|
48
71
|
if respond_to?(setter)
|
|
49
|
-
|
|
72
|
+
raise
|
|
50
73
|
else
|
|
51
|
-
|
|
74
|
+
attribute_writer_missing(k.to_s, v)
|
|
52
75
|
end
|
|
53
76
|
end
|
|
54
77
|
end
|