activemodel 7.0.8.7 → 7.1.5.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +174 -167
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +5 -5
  7. data/lib/active_model/attribute/user_provided_default.rb +4 -0
  8. data/lib/active_model/attribute.rb +26 -1
  9. data/lib/active_model/attribute_assignment.rb +1 -1
  10. data/lib/active_model/attribute_methods.rb +134 -73
  11. data/lib/active_model/attribute_registration.rb +77 -0
  12. data/lib/active_model/attribute_set.rb +10 -1
  13. data/lib/active_model/attributes.rb +65 -48
  14. data/lib/active_model/callbacks.rb +5 -5
  15. data/lib/active_model/conversion.rb +14 -4
  16. data/lib/active_model/deprecator.rb +7 -0
  17. data/lib/active_model/dirty.rb +134 -13
  18. data/lib/active_model/error.rb +4 -3
  19. data/lib/active_model/errors.rb +37 -6
  20. data/lib/active_model/forbidden_attributes_protection.rb +2 -0
  21. data/lib/active_model/gem_version.rb +4 -4
  22. data/lib/active_model/lint.rb +1 -1
  23. data/lib/active_model/locale/en.yml +1 -0
  24. data/lib/active_model/model.rb +34 -2
  25. data/lib/active_model/naming.rb +29 -10
  26. data/lib/active_model/railtie.rb +4 -0
  27. data/lib/active_model/secure_password.rb +61 -23
  28. data/lib/active_model/serialization.rb +3 -3
  29. data/lib/active_model/serializers/json.rb +1 -1
  30. data/lib/active_model/translation.rb +18 -16
  31. data/lib/active_model/type/big_integer.rb +23 -1
  32. data/lib/active_model/type/binary.rb +7 -1
  33. data/lib/active_model/type/boolean.rb +11 -9
  34. data/lib/active_model/type/date.rb +28 -2
  35. data/lib/active_model/type/date_time.rb +45 -3
  36. data/lib/active_model/type/decimal.rb +39 -1
  37. data/lib/active_model/type/float.rb +30 -1
  38. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
  39. data/lib/active_model/type/helpers/numeric.rb +6 -1
  40. data/lib/active_model/type/helpers/time_value.rb +28 -12
  41. data/lib/active_model/type/immutable_string.rb +37 -1
  42. data/lib/active_model/type/integer.rb +44 -1
  43. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  44. data/lib/active_model/type/string.rb +9 -1
  45. data/lib/active_model/type/time.rb +48 -7
  46. data/lib/active_model/type/value.rb +17 -1
  47. data/lib/active_model/type.rb +1 -0
  48. data/lib/active_model/validations/absence.rb +1 -1
  49. data/lib/active_model/validations/acceptance.rb +1 -1
  50. data/lib/active_model/validations/callbacks.rb +4 -4
  51. data/lib/active_model/validations/clusivity.rb +5 -8
  52. data/lib/active_model/validations/comparability.rb +0 -11
  53. data/lib/active_model/validations/comparison.rb +15 -7
  54. data/lib/active_model/validations/format.rb +6 -7
  55. data/lib/active_model/validations/length.rb +10 -8
  56. data/lib/active_model/validations/numericality.rb +35 -23
  57. data/lib/active_model/validations/presence.rb +1 -1
  58. data/lib/active_model/validations/resolve_value.rb +26 -0
  59. data/lib/active_model/validations/validates.rb +4 -4
  60. data/lib/active_model/validations/with.rb +9 -2
  61. data/lib/active_model/validations.rb +44 -9
  62. data/lib/active_model/validator.rb +7 -5
  63. data/lib/active_model/version.rb +1 -1
  64. data/lib/active_model.rb +5 -1
  65. metadata +11 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f65c2aa43d05d67e900b3b12ac60c411899082b9a0570f86cac06af8f4375abd
4
- data.tar.gz: d0b726b2429632680b2c9d12cdcbf652b536c00e192b983a9c5ccd7da2fdae34
3
+ metadata.gz: 9f79873d7c2a9772e4c9ca33cf468880b923a818c11b874eb7c8c40954a1e652
4
+ data.tar.gz: a264d11a6a4a6783d08513145436a78440847774f64ab8bfe5436e0e0c56224b
5
5
  SHA512:
6
- metadata.gz: bbb5722f3e0928497062d7672d917d845127309decbc209415c4f8785a48d7ec2520441c5acc9ca6dea91a412d3b57423d3d6f88ea1e0903959c9fb108ab6307
7
- data.tar.gz: cbebd0f6c2c12e2cfe7725654a1bd7c88894492aef219fce5c7b168e53ed008f430117cd2e9da2f0e8fd3e229501b6314d3e7320742a792dea5bfa46dd2a994c
6
+ metadata.gz: 7019a3654dabd54a630605e8b032450f170fe5aa8f3fa0ec626f80546dbf9c09cea143bed32356608319b059127ede72aae6e08c5a280dfae9021e98f027640f
7
+ data.tar.gz: 72936b14496e7b9465b6cdaf7894b80b502bb275e58f7e4b9e52d67ccfd671ee1a4f952ea3da25b504147d81a645755a4cb16dde63f4ecbc68b6787636201b0b
data/CHANGELOG.md CHANGED
@@ -1,299 +1,306 @@
1
- ## Rails 7.0.8.7 (December 10, 2024) ##
1
+ ## Rails 7.1.5.1 (December 10, 2024) ##
2
2
 
3
3
  * No changes.
4
4
 
5
5
 
6
- ## Rails 7.0.8.6 (October 23, 2024) ##
7
-
8
- * No changes.
9
-
10
-
11
- ## Rails 7.0.8.5 (October 15, 2024) ##
12
-
13
- * No changes.
6
+ ## Rails 7.1.5 (October 30, 2024) ##
14
7
 
8
+ * Fix regression in `alias_attribute` to work with user defined methods.
15
9
 
16
- ## Rails 7.0.8.4 (June 04, 2024) ##
17
-
18
- * No changes.
10
+ `alias_attribute` would wrongly assume the attribute accessor was generated by Active Model.
19
11
 
12
+ ```ruby
13
+ class Person
14
+ include ActiveModel::AttributeMethods
20
15
 
21
- ## Rails 7.0.8.3 (May 17, 2024) ##
22
-
23
- * No changes.
24
-
25
-
26
- ## Rails 7.0.8.2 (May 16, 2024) ##
27
-
28
- * No changes.
29
-
30
-
31
- ## Rails 7.0.8.1 (February 21, 2024) ##
32
-
33
- * No changes.
16
+ define_attribute_methods :name
17
+ attr_accessor :name
34
18
 
19
+ alias_attribute :full_name, :name
20
+ end
35
21
 
36
- ## Rails 7.0.8 (September 09, 2023) ##
22
+ person.full_name # => NoMethodError: undefined method `attribute' for an instance of Person
23
+ ```
37
24
 
38
- * No changes.
25
+ *Jean Boussier*
39
26
 
40
27
 
41
- ## Rails 7.0.7.2 (August 22, 2023) ##
28
+ ## Rails 7.1.4.2 (October 23, 2024) ##
42
29
 
43
30
  * No changes.
44
31
 
45
32
 
46
- ## Rails 7.0.7.1 (August 22, 2023) ##
33
+ ## Rails 7.1.4.1 (October 15, 2024) ##
47
34
 
48
35
  * No changes.
49
36
 
50
37
 
51
- ## Rails 7.0.7 (August 09, 2023) ##
52
-
53
- * Error.full_message now strips ":base" from the message.
54
-
55
- *zzak*
56
-
57
- * Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
58
- `ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
59
-
60
-
61
- ## Rails 7.0.6 (June 29, 2023) ##
38
+ ## Rails 7.1.4 (August 22, 2024) ##
62
39
 
63
40
  * No changes.
64
41
 
65
42
 
66
- ## Rails 7.0.5.1 (June 26, 2023) ##
43
+ ## Rails 7.1.3.4 (June 04, 2024) ##
67
44
 
68
45
  * No changes.
69
46
 
70
47
 
71
- ## Rails 7.0.5 (May 24, 2023) ##
48
+ ## Rails 7.1.3.3 (May 16, 2024) ##
72
49
 
73
50
  * No changes.
74
51
 
75
52
 
76
- ## Rails 7.0.4.3 (March 13, 2023) ##
53
+ ## Rails 7.1.3.2 (February 21, 2024) ##
77
54
 
78
55
  * No changes.
79
56
 
80
57
 
81
- ## Rails 7.0.4.2 (January 24, 2023) ##
58
+ ## Rails 7.1.3.1 (February 21, 2024) ##
82
59
 
83
60
  * No changes.
84
61
 
85
62
 
86
- ## Rails 7.0.4.1 (January 17, 2023) ##
63
+ ## Rails 7.1.3 (January 16, 2024) ##
87
64
 
88
65
  * No changes.
89
66
 
90
67
 
91
- ## Rails 7.0.4 (September 09, 2022) ##
68
+ ## Rails 7.1.2 (November 10, 2023) ##
92
69
 
93
- * Handle name clashes in attribute methods code generation cache.
70
+ * Make `==(other)` method of AttributeSet safe.
94
71
 
95
- When two distinct attribute methods would generate similar names,
96
- the first implementation would be incorrectly re-used.
72
+ *Dmitry Pogrebnoy*
97
73
 
98
- ```ruby
99
- class A
100
- attribute_method_suffix "_changed?"
101
- define_attribute_methods :x
102
- end
103
74
 
104
- class B
105
- attribute_method_suffix "?"
106
- define_attribute_methods :x_changed
107
- end
108
- ```
109
-
110
- *Jean Boussier*
111
-
112
- ## Rails 7.0.3.1 (July 12, 2022) ##
75
+ ## Rails 7.1.1 (October 11, 2023) ##
113
76
 
114
77
  * No changes.
115
78
 
116
79
 
117
- ## Rails 7.0.3 (May 09, 2022) ##
80
+ ## Rails 7.1.0 (October 05, 2023) ##
118
81
 
119
82
  * No changes.
120
83
 
121
84
 
122
- ## Rails 7.0.2.4 (April 26, 2022) ##
85
+ ## Rails 7.1.0.rc2 (October 01, 2023) ##
123
86
 
124
87
  * No changes.
125
88
 
126
89
 
127
- ## Rails 7.0.2.3 (March 08, 2022) ##
128
-
129
- * No changes.
90
+ ## Rails 7.1.0.rc1 (September 27, 2023) ##
130
91
 
92
+ * Remove change in the typography of user facing error messages.
93
+ For example, “can’t be blank” is again “can't be blank”.
131
94
 
132
- ## Rails 7.0.2.2 (February 11, 2022) ##
133
-
134
- * No changes.
135
-
95
+ *Rafael Mendonça França*
136
96
 
137
- ## Rails 7.0.2.1 (February 11, 2022) ##
138
97
 
139
- * No changes.
98
+ ## Rails 7.1.0.beta1 (September 13, 2023) ##
140
99
 
100
+ * Support composite identifiers in `to_key`
141
101
 
142
- ## Rails 7.0.2 (February 08, 2022) ##
102
+ `to_key` avoids wrapping `#id` value into an `Array` if `#id` already an array
143
103
 
144
- * Use different cache namespace for proxy calls
104
+ *Nikita Vasilevsky*
145
105
 
146
- Models can currently have different attribute bodies for the same method
147
- names, leading to conflicts. Adding a new namespace `:active_model_proxy`
148
- fixes the issue.
106
+ * Add `ActiveModel::Conversion.param_delimiter` to configure delimiter being used in `to_param`
149
107
 
150
- *Chris Salzberg*
108
+ *Nikita Vasilevsky*
151
109
 
110
+ * `undefine_attribute_methods` undefines alias attribute methods along with attribute methods.
152
111
 
153
- ## Rails 7.0.1 (January 06, 2022) ##
112
+ *Nikita Vasilevsky*
154
113
 
155
- * No changes.
114
+ * Error.full_message now strips ":base" from the message.
156
115
 
116
+ *zzak*
157
117
 
158
- ## Rails 7.0.0 (December 15, 2021) ##
118
+ * Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
119
+ `ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
159
120
 
160
- * No changes.
121
+ *Lewis Buckley*
161
122
 
123
+ * Improve password length validation in ActiveModel::SecurePassword to consider byte size for BCrypt
124
+ compatibility.
162
125
 
163
- ## Rails 7.0.0.rc3 (December 14, 2021) ##
126
+ The previous password length validation only considered the character count, which may not
127
+ accurately reflect the 72-byte size limit imposed by BCrypt. This change updates the validation
128
+ to consider both character count and byte size while keeping the character length validation in place.
164
129
 
165
- * No changes.
130
+ ```ruby
131
+ user = User.new(password: "a" * 73) # 73 characters
132
+ user.valid? # => false
133
+ user.errors[:password] # => ["is too long"]
166
134
 
167
135
 
168
- ## Rails 7.0.0.rc2 (December 14, 2021) ##
136
+ user = User.new(password: "あ" * 25) # 25 characters, 75 bytes
137
+ user.valid? # => false
138
+ user.errors[:password] # => ["is too long"]
139
+ ```
169
140
 
170
- * No changes.
141
+ *ChatGPT*, *Guillermo Iguaran*
171
142
 
172
- ## Rails 7.0.0.rc1 (December 06, 2021) ##
143
+ * `has_secure_password` now generates an `#{attribute}_salt` method that returns the salt
144
+ used to compute the password digest. The salt will change whenever the password is changed,
145
+ so it can be used to create single-use password reset tokens with `generates_token_for`:
173
146
 
174
- * Remove support to Marshal load Rails 5.x `ActiveModel::AttributeSet` format.
147
+ ```ruby
148
+ class User < ActiveRecord::Base
149
+ has_secure_password
175
150
 
176
- *Rafael Mendonça França*
151
+ generates_token_for :password_reset, expires_in: 15.minutes do
152
+ password_salt&.last(10)
153
+ end
154
+ end
155
+ ```
177
156
 
178
- * Remove support to Marshal and YAML load Rails 5.x error format.
157
+ *Lázaro Nixon*
179
158
 
180
- *Rafael Mendonça França*
159
+ * Improve typography of user facing error messages. In English contractions,
160
+ the Unicode APOSTROPHE (`U+0027`) is now RIGHT SINGLE QUOTATION MARK
161
+ (`U+2019`). For example, "can't be blank" is now "can’t be blank".
181
162
 
182
- * Remove deprecated support to use `[]=` in `ActiveModel::Errors#messages`.
163
+ *Jon Dufresne*
183
164
 
184
- *Rafael Mendonça França*
165
+ * Add class to `ActiveModel::MissingAttributeError` error message.
185
166
 
186
- * Remove deprecated support to `delete` errors from `ActiveModel::Errors#messages`.
167
+ Show which class is missing the attribute in the error message:
187
168
 
188
- *Rafael Mendonça França*
169
+ ```ruby
170
+ user = User.first
171
+ user.pets.select(:id).first.user_id
172
+ # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
173
+ ```
189
174
 
190
- * Remove deprecated support to `clear` errors from `ActiveModel::Errors#messages`.
175
+ *Petrik de Heus*
191
176
 
192
- *Rafael Mendonça França*
177
+ * Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
178
+ results.
193
179
 
194
- * Remove deprecated support concat errors to `ActiveModel::Errors#messages`.
180
+ *Vasiliy Ermolovich*
195
181
 
196
- *Rafael Mendonça França*
182
+ * Custom attribute types that inherit from Active Model built-in types and do
183
+ not override the `serialize` method will now benefit from an optimization
184
+ when serializing attribute values for the database.
197
185
 
198
- * Remove deprecated `ActiveModel::Errors#to_xml`.
186
+ For example, with a custom type like the following:
199
187
 
200
- *Rafael Mendonça França*
201
-
202
- * Remove deprecated `ActiveModel::Errors#keys`.
188
+ ```ruby
189
+ class DowncasedString < ActiveModel::Type::String
190
+ def cast(value)
191
+ super&.downcase
192
+ end
193
+ end
203
194
 
204
- *Rafael Mendonça França*
195
+ ActiveRecord::Type.register(:downcased_string, DowncasedString)
205
196
 
206
- * Remove deprecated `ActiveModel::Errors#values`.
197
+ class User < ActiveRecord::Base
198
+ attribute :email, :downcased_string
199
+ end
207
200
 
208
- *Rafael Mendonça França*
201
+ user = User.new(email: "FooBar@example.com")
202
+ ```
209
203
 
210
- * Remove deprecated `ActiveModel::Errors#slice!`.
204
+ Serializing the `email` attribute for the database will be roughly twice as
205
+ fast. More expensive `cast` operations will likely see greater improvements.
211
206
 
212
- *Rafael Mendonça França*
207
+ *Jonathan Hefner*
213
208
 
214
- * Remove deprecated `ActiveModel::Errors#to_h`.
209
+ * `has_secure_password` now supports password challenges via a
210
+ `password_challenge` accessor and validation.
215
211
 
216
- *Rafael Mendonça França*
212
+ A password challenge is a safeguard to verify that the current user is
213
+ actually the password owner. It can be used when changing sensitive model
214
+ fields, such as the password itself. It is different than a password
215
+ confirmation, which is used to prevent password typos.
217
216
 
218
- * Remove deprecated enumeration of `ActiveModel::Errors` instances as a Hash.
217
+ When `password_challenge` is set, the validation checks that the value's
218
+ digest matches the *currently persisted* `password_digest` (i.e.
219
+ `password_digest_was`).
219
220
 
220
- *Rafael Mendonça França*
221
+ This allows a password challenge to be done as part of a typical `update`
222
+ call, just like a password confirmation. It also allows a password
223
+ challenge error to be handled in the same way as other validation errors.
221
224
 
222
- * Clear secure password cache if password is set to `nil`
225
+ For example, in the controller, instead of:
223
226
 
224
- Before:
227
+ ```ruby
228
+ password_params = params.require(:password).permit(
229
+ :password_challenge,
230
+ :password,
231
+ :password_confirmation,
232
+ )
225
233
 
226
- user.password = 'something'
227
- user.password = nil
234
+ password_challenge = password_params.delete(:password_challenge)
235
+ @password_challenge_failed = !current_user.authenticate(password_challenge)
228
236
 
229
- user.password # => 'something'
237
+ if !@password_challenge_failed && current_user.update(password_params)
238
+ # ...
239
+ end
240
+ ```
230
241
 
231
- Now:
242
+ You can now write:
232
243
 
233
- user.password = 'something'
234
- user.password = nil
244
+ ```ruby
245
+ password_params = params.require(:password).permit(
246
+ :password_challenge,
247
+ :password,
248
+ :password_confirmation,
249
+ ).with_defaults(password_challenge: "")
250
+
251
+ if current_user.update(password_params)
252
+ # ...
253
+ end
254
+ ```
235
255
 
236
- user.password # => nil
256
+ And, in the view, instead of checking `@password_challenge_failed`, you can
257
+ render an error for the `password_challenge` field just as you would for
258
+ other form fields, including utilizing `config.action_view.field_error_proc`.
237
259
 
238
- *Markus Doits*
260
+ *Jonathan Hefner*
239
261
 
240
- ## Rails 7.0.0.alpha2 (September 15, 2021) ##
262
+ * Support infinite ranges for `LengthValidator`s `:in`/`:within` options
241
263
 
242
- * No changes.
264
+ ```ruby
265
+ validates_length_of :first_name, in: ..30
266
+ ```
243
267
 
268
+ *fatkodima*
244
269
 
245
- ## Rails 7.0.0.alpha1 (September 15, 2021) ##
270
+ * Add support for beginless ranges to inclusivity/exclusivity validators:
246
271
 
247
- * Introduce `ActiveModel::API`.
272
+ ```ruby
273
+ validates_inclusion_of :birth_date, in: -> { (..Date.today) }
274
+ ```
248
275
 
249
- Make `ActiveModel::API` the minimum API to talk with Action Pack and Action View.
250
- This will allow adding more functionality to `ActiveModel::Model`.
276
+ ```ruby
277
+ validates_exclusion_of :birth_date, in: -> { (..Date.today) }
278
+ ```
251
279
 
252
- *Petrik de Heus*, *Nathaniel Watts*
280
+ *Bo Jeanes*
253
281
 
254
- * Fix dirty check for Float::NaN and BigDecimal::NaN.
282
+ * Make validators accept lambdas without record argument
255
283
 
256
- Float::NaN and BigDecimal::NaN in Ruby are [special values](https://bugs.ruby-lang.org/issues/1720)
257
- and can't be compared with `==`.
284
+ ```ruby
285
+ # Before
286
+ validates_comparison_of :birth_date, less_than_or_equal_to: ->(_record) { Date.today }
258
287
 
259
- *Marcelo Lauxen*
288
+ # After
289
+ validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
290
+ ```
260
291
 
261
- * Fix `to_json` for `ActiveModel::Dirty` object.
292
+ *fatkodima*
262
293
 
263
- Exclude `mutations_from_database` attribute from json as it lead to recursion.
294
+ * Fix casting long strings to `Date`, `Time` or `DateTime`
264
295
 
265
- *Anil Maurya*
296
+ *fatkodima*
266
297
 
267
- * Add `ActiveModel::AttributeSet#values_for_database`.
298
+ * Use different cache namespace for proxy calls
268
299
 
269
- Returns attributes with values for assignment to the database.
300
+ Models can currently have different attribute bodies for the same method
301
+ names, leading to conflicts. Adding a new namespace `:active_model_proxy`
302
+ fixes the issue.
270
303
 
271
304
  *Chris Salzberg*
272
305
 
273
- * Fix delegation in ActiveModel::Type::Registry#lookup and ActiveModel::Type.lookup.
274
-
275
- Passing a last positional argument `{}` would be incorrectly considered as keyword argument.
276
-
277
- *Benoit Daloze*
278
-
279
- * Cache and re-use generated attribute methods.
280
-
281
- Generated methods with identical implementations will now share their instruction sequences
282
- leading to reduced memory retention, and slightly faster load time.
283
-
284
- *Jean Boussier*
285
-
286
- * Add `in: range` parameter to `numericality` validator.
287
-
288
- *Michal Papis*
289
-
290
- * Add `locale` argument to `ActiveModel::Name#initialize` to be used to generate the `singular`,
291
- `plural`, `route_key` and `singular_route_key` values.
292
-
293
- *Lukas Pokorny*
294
-
295
- * Make ActiveModel::Errors#inspect slimmer for readability
296
-
297
- *lulalala*
298
-
299
- Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/activemodel/CHANGELOG.md) for previous changes.
306
+ Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2022 David Heinemeier Hansson
1
+ Copyright (c) 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
@@ -1,22 +1,22 @@
1
- = Active Model -- model interfaces for Rails
1
+ = Active Model -- model interfaces for \Rails
2
2
 
3
3
  Active Model provides a known set of interfaces for usage in model classes.
4
4
  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
- the Rails framework.
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.
8
+ You can read more about Active Model in the {Active Model Basics}[https://guides.rubyonrails.org/active_model_basics.html] guide.
9
9
 
10
- Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
10
+ Prior to \Rails 3.0, if a plugin or gem developer wanted to have an object
11
11
  interact with Action Pack helpers, it was required to either copy chunks of
12
- code from Rails, or monkey patch entire helpers to make them handle objects
12
+ code from \Rails, or monkey patch entire helpers to make them handle objects
13
13
  that did not exactly conform to the Active Record interface. This would result
14
14
  in code duplication and fragile applications that broke on upgrades. Active
15
15
  Model solves this by defining an explicit API. You can read more about the
16
- API in <tt>ActiveModel::Lint::Tests</tt>.
16
+ API in +ActiveModel::Lint::Tests+.
17
17
 
18
18
  Active Model provides a default module that implements the basic API required
19
- to integrate with Action Pack out of the box: <tt>ActiveModel::API</tt>.
19
+ to integrate with Action Pack out of the box: ActiveModel::API.
20
20
 
21
21
  class Person
22
22
  include ActiveModel::API
@@ -32,7 +32,7 @@ to integrate with Action Pack out of the box: <tt>ActiveModel::API</tt>.
32
32
 
33
33
  It includes model name introspections, conversions, translations and
34
34
  validations, resulting in a class suitable to be used with Action Pack.
35
- See <tt>ActiveModel::API</tt> for more examples.
35
+ See ActiveModel::API for more examples.
36
36
 
37
37
  Active Model also provides the following functionality to have ORM-like
38
38
  behavior out of the box:
@@ -156,7 +156,7 @@ behavior out of the box:
156
156
 
157
157
  * Making objects serializable
158
158
 
159
- <tt>ActiveModel::Serialization</tt> provides a standard interface for your object
159
+ ActiveModel::Serialization provides a standard interface for your object
160
160
  to provide +to_json+ serialization.
161
161
 
162
162
  class SerialPerson
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
4
+ require "active_support/core_ext/hash/indifferent_access"
5
+
6
+ module ActiveModel
7
+ module Access # :nodoc:
8
+ def slice(*methods)
9
+ methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
10
+ end
11
+
12
+ def values_at(*methods)
13
+ methods.flatten.map! { |method| public_send(method) }
14
+ end
15
+ end
16
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModel
4
- # == Active \Model \API
4
+ # = Active \Model \API
5
5
  #
6
6
  # Includes the required interface for an object to interact with
7
- # Action Pack and Action View, using different Active Model modules.
7
+ # Action Pack and Action View, using different Active \Model modules.
8
8
  # It includes model name introspections, conversions, translations, and
9
9
  # validations. Besides that, it allows you to initialize the object with a
10
10
  # hash of attributes, pretty much like Active Record does.
@@ -20,7 +20,7 @@ module ActiveModel
20
20
  # person.name # => "bob"
21
21
  # person.age # => "18"
22
22
  #
23
- # Note that, by default, <tt>ActiveModel::API</tt> implements <tt>persisted?</tt>
23
+ # Note that, by default, +ActiveModel::API+ implements #persisted?
24
24
  # to return +false+, which is the most common case. You may want to override
25
25
  # it in your class to simulate a different scenario:
26
26
  #
@@ -36,7 +36,7 @@ module ActiveModel
36
36
  # person = Person.new(id: 1, name: 'bob')
37
37
  # person.persisted? # => true
38
38
  #
39
- # Also, if for some reason you need to run code on <tt>initialize</tt>, make
39
+ # Also, if for some reason you need to run code on initialize ( ::new ), make
40
40
  # sure you call +super+ if you want the attributes hash initialization to
41
41
  # happen.
42
42
  #
@@ -54,7 +54,7 @@ module ActiveModel
54
54
  # person.omg # => true
55
55
  #
56
56
  # For more detailed information on other functionalities available, please
57
- # refer to the specific modules included in <tt>ActiveModel::API</tt>
57
+ # refer to the specific modules included in +ActiveModel::API+
58
58
  # (see below).
59
59
  module API
60
60
  extend ActiveSupport::Concern
@@ -4,6 +4,10 @@ require "active_model/attribute"
4
4
 
5
5
  module ActiveModel
6
6
  class Attribute # :nodoc:
7
+ def with_user_default(value)
8
+ UserProvidedDefault.new(name, value, type, self.is_a?(FromDatabase) ? self : original_attribute)
9
+ end
10
+
7
11
  class UserProvidedDefault < FromUser # :nodoc:
8
12
  def initialize(name, value, type, database_default)
9
13
  @user_provided_value = value
@@ -53,7 +53,10 @@ module ActiveModel
53
53
  end
54
54
 
55
55
  def value_for_database
56
- type.serialize(value)
56
+ if !defined?(@value_for_database) || type.changed_in_place?(@value_for_database, value)
57
+ @value_for_database = _value_for_database
58
+ end
59
+ @value_for_database
57
60
  end
58
61
 
59
62
  def serializable?(&block)
@@ -159,6 +162,10 @@ module ActiveModel
159
162
  assigned? && type.changed?(original_value, value, value_before_type_cast)
160
163
  end
161
164
 
165
+ def _value_for_database
166
+ type.serialize(value)
167
+ end
168
+
162
169
  def _original_value_for_database
163
170
  type.serialize(original_value)
164
171
  end
@@ -168,6 +175,19 @@ module ActiveModel
168
175
  type.deserialize(value)
169
176
  end
170
177
 
178
+ def forgetting_assignment
179
+ # If this attribute was not persisted (with a `value_for_database`
180
+ # that might differ from `value_before_type_cast`) and `value` has not
181
+ # changed in place, we can use the existing `value_before_type_cast`
182
+ # to avoid deserialize / cast / serialize calls from computing the new
183
+ # attribute's `value_before_type_cast`.
184
+ if !defined?(@value_for_database) && !changed_in_place?
185
+ with_value_from_database(value_before_type_cast)
186
+ else
187
+ super
188
+ end
189
+ end
190
+
171
191
  private
172
192
  def _original_value_for_database
173
193
  value_before_type_cast
@@ -182,6 +202,11 @@ module ActiveModel
182
202
  def came_from_user?
183
203
  !type.value_constructed_by_mass_assignment?(value_before_type_cast)
184
204
  end
205
+
206
+ private
207
+ def _value_for_database
208
+ Type::SerializeCastValue.serialize(type, value)
209
+ end
185
210
  end
186
211
 
187
212
  class WithCastValue < Attribute # :nodoc: