activemodel 7.0.8.1 → 7.1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -170
  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 +102 -63
  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 +62 -45
  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 +12 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17347de91789065079fce9331c6ccfdc729b7834a7b6d39042b21b2d38cb4ca5
4
- data.tar.gz: 7542e11363bf21320cf339d0757a29ec83dd91ab6296c6e803e89e2980f8d9ad
3
+ metadata.gz: 6f0a075e086d886dd9b16a9232ea0c5148638253514fd3337206b8b65acf5b2c
4
+ data.tar.gz: fe360be08655f696cc0db91737662c4cbe15ee6d95f25b70344380189cd0d8c4
5
5
  SHA512:
6
- metadata.gz: 69c268146ff4917f0e9ea1a0692cac788494881ec3c9c89e9d8e1f1bb7fdec9496ed5d3a117ed9dcfe9ebce4eef1674f73f4d5f5e3a3761c3785616c0cd8f812
7
- data.tar.gz: 45c9c44652e2fc0d6239d861159ba6fdce025b63c854ff43134c22560a7a3073d1aa7e4ac1b272154b9dde9db351ea25532e83e22ba146ba787da5c1f8985327
6
+ metadata.gz: bca01703e1f710950818b6ecfbf0e9bd78af97bd268619150610403aebf7c02f87f66f45ebbffe7f4f63b1a7c5179972e6db057239db25de506dfb36ee5b247d
7
+ data.tar.gz: 330a283aee551930eaf626b976191271652f66b2a7dbfc7190b016054eb81353918e42b9014225c4f988d799bb50a8466d2d3e55ba9899cb58712fd548453972
data/CHANGELOG.md CHANGED
@@ -1,269 +1,254 @@
1
- ## Rails 7.0.8.1 (February 21, 2024) ##
1
+ ## Rails 7.1.3.2 (February 21, 2024) ##
2
2
 
3
3
  * No changes.
4
4
 
5
5
 
6
- ## Rails 7.0.8 (September 09, 2023) ##
6
+ ## Rails 7.1.3.1 (February 21, 2024) ##
7
7
 
8
8
  * No changes.
9
9
 
10
10
 
11
- ## Rails 7.0.7.2 (August 22, 2023) ##
11
+ ## Rails 7.1.3 (January 16, 2024) ##
12
12
 
13
13
  * No changes.
14
14
 
15
15
 
16
- ## Rails 7.0.7.1 (August 22, 2023) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 7.0.7 (August 09, 2023) ##
22
-
23
- * Error.full_message now strips ":base" from the message.
24
-
25
- *zzak*
26
-
27
- * Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
28
- `ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
29
-
30
-
31
- ## Rails 7.0.6 (June 29, 2023) ##
32
-
33
- * No changes.
16
+ ## Rails 7.1.2 (November 10, 2023) ##
34
17
 
18
+ * Make `==(other)` method of AttributeSet safe.
35
19
 
36
- ## Rails 7.0.5.1 (June 26, 2023) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 7.0.5 (May 24, 2023) ##
42
-
43
- * No changes.
20
+ *Dmitry Pogrebnoy*
44
21
 
45
22
 
46
- ## Rails 7.0.4.3 (March 13, 2023) ##
23
+ ## Rails 7.1.1 (October 11, 2023) ##
47
24
 
48
25
  * No changes.
49
26
 
50
27
 
51
- ## Rails 7.0.4.2 (January 24, 2023) ##
28
+ ## Rails 7.1.0 (October 05, 2023) ##
52
29
 
53
30
  * No changes.
54
31
 
55
32
 
56
- ## Rails 7.0.4.1 (January 17, 2023) ##
33
+ ## Rails 7.1.0.rc2 (October 01, 2023) ##
57
34
 
58
35
  * No changes.
59
36
 
60
37
 
61
- ## Rails 7.0.4 (September 09, 2022) ##
62
-
63
- * Handle name clashes in attribute methods code generation cache.
64
-
65
- When two distinct attribute methods would generate similar names,
66
- the first implementation would be incorrectly re-used.
67
-
68
- ```ruby
69
- class A
70
- attribute_method_suffix "_changed?"
71
- define_attribute_methods :x
72
- end
73
-
74
- class B
75
- attribute_method_suffix "?"
76
- define_attribute_methods :x_changed
77
- end
78
- ```
79
-
80
- *Jean Boussier*
38
+ ## Rails 7.1.0.rc1 (September 27, 2023) ##
81
39
 
82
- ## Rails 7.0.3.1 (July 12, 2022) ##
40
+ * Remove change in the typography of user facing error messages.
41
+ For example, “can’t be blank” is again “can't be blank”.
83
42
 
84
- * No changes.
85
-
86
-
87
- ## Rails 7.0.3 (May 09, 2022) ##
88
-
89
- * No changes.
90
-
91
-
92
- ## Rails 7.0.2.4 (April 26, 2022) ##
93
-
94
- * No changes.
95
-
96
-
97
- ## Rails 7.0.2.3 (March 08, 2022) ##
98
-
99
- * No changes.
100
-
101
-
102
- ## Rails 7.0.2.2 (February 11, 2022) ##
103
-
104
- * No changes.
105
-
106
-
107
- ## Rails 7.0.2.1 (February 11, 2022) ##
43
+ *Rafael Mendonça França*
108
44
 
109
- * No changes.
110
45
 
46
+ ## Rails 7.1.0.beta1 (September 13, 2023) ##
111
47
 
112
- ## Rails 7.0.2 (February 08, 2022) ##
48
+ * Support composite identifiers in `to_key`
113
49
 
114
- * Use different cache namespace for proxy calls
50
+ `to_key` avoids wrapping `#id` value into an `Array` if `#id` already an array
115
51
 
116
- Models can currently have different attribute bodies for the same method
117
- names, leading to conflicts. Adding a new namespace `:active_model_proxy`
118
- fixes the issue.
52
+ *Nikita Vasilevsky*
119
53
 
120
- *Chris Salzberg*
54
+ * Add `ActiveModel::Conversion.param_delimiter` to configure delimiter being used in `to_param`
121
55
 
56
+ *Nikita Vasilevsky*
122
57
 
123
- ## Rails 7.0.1 (January 06, 2022) ##
58
+ * `undefine_attribute_methods` undefines alias attribute methods along with attribute methods.
124
59
 
125
- * No changes.
60
+ *Nikita Vasilevsky*
126
61
 
62
+ * Error.full_message now strips ":base" from the message.
127
63
 
128
- ## Rails 7.0.0 (December 15, 2021) ##
64
+ *zzak*
129
65
 
130
- * No changes.
66
+ * Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
67
+ `ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
131
68
 
69
+ *Lewis Buckley*
132
70
 
133
- ## Rails 7.0.0.rc3 (December 14, 2021) ##
71
+ * Improve password length validation in ActiveModel::SecurePassword to consider byte size for BCrypt
72
+ compatibility.
134
73
 
135
- * No changes.
74
+ The previous password length validation only considered the character count, which may not
75
+ accurately reflect the 72-byte size limit imposed by BCrypt. This change updates the validation
76
+ to consider both character count and byte size while keeping the character length validation in place.
136
77
 
78
+ ```ruby
79
+ user = User.new(password: "a" * 73) # 73 characters
80
+ user.valid? # => false
81
+ user.errors[:password] # => ["is too long"]
137
82
 
138
- ## Rails 7.0.0.rc2 (December 14, 2021) ##
139
83
 
140
- * No changes.
84
+ user = User.new(password: "あ" * 25) # 25 characters, 75 bytes
85
+ user.valid? # => false
86
+ user.errors[:password] # => ["is too long"]
87
+ ```
141
88
 
142
- ## Rails 7.0.0.rc1 (December 06, 2021) ##
89
+ *ChatGPT*, *Guillermo Iguaran*
143
90
 
144
- * Remove support to Marshal load Rails 5.x `ActiveModel::AttributeSet` format.
91
+ * `has_secure_password` now generates an `#{attribute}_salt` method that returns the salt
92
+ used to compute the password digest. The salt will change whenever the password is changed,
93
+ so it can be used to create single-use password reset tokens with `generates_token_for`:
145
94
 
146
- *Rafael Mendonça França*
95
+ ```ruby
96
+ class User < ActiveRecord::Base
97
+ has_secure_password
147
98
 
148
- * Remove support to Marshal and YAML load Rails 5.x error format.
99
+ generates_token_for :password_reset, expires_in: 15.minutes do
100
+ password_salt&.last(10)
101
+ end
102
+ end
103
+ ```
149
104
 
150
- *Rafael Mendonça França*
105
+ *Lázaro Nixon*
151
106
 
152
- * Remove deprecated support to use `[]=` in `ActiveModel::Errors#messages`.
107
+ * Improve typography of user facing error messages. In English contractions,
108
+ the Unicode APOSTROPHE (`U+0027`) is now RIGHT SINGLE QUOTATION MARK
109
+ (`U+2019`). For example, "can't be blank" is now "can’t be blank".
153
110
 
154
- *Rafael Mendonça França*
111
+ *Jon Dufresne*
155
112
 
156
- * Remove deprecated support to `delete` errors from `ActiveModel::Errors#messages`.
113
+ * Add class to `ActiveModel::MissingAttributeError` error message.
157
114
 
158
- *Rafael Mendonça França*
115
+ Show which class is missing the attribute in the error message:
159
116
 
160
- * Remove deprecated support to `clear` errors from `ActiveModel::Errors#messages`.
117
+ ```ruby
118
+ user = User.first
119
+ user.pets.select(:id).first.user_id
120
+ # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
121
+ ```
161
122
 
162
- *Rafael Mendonça França*
123
+ *Petrik de Heus*
163
124
 
164
- * Remove deprecated support concat errors to `ActiveModel::Errors#messages`.
125
+ * Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
126
+ results.
165
127
 
166
- *Rafael Mendonça França*
128
+ *Vasiliy Ermolovich*
167
129
 
168
- * Remove deprecated `ActiveModel::Errors#to_xml`.
130
+ * Custom attribute types that inherit from Active Model built-in types and do
131
+ not override the `serialize` method will now benefit from an optimization
132
+ when serializing attribute values for the database.
169
133
 
170
- *Rafael Mendonça França*
134
+ For example, with a custom type like the following:
171
135
 
172
- * Remove deprecated `ActiveModel::Errors#keys`.
136
+ ```ruby
137
+ class DowncasedString < ActiveModel::Type::String
138
+ def cast(value)
139
+ super&.downcase
140
+ end
141
+ end
173
142
 
174
- *Rafael Mendonça França*
143
+ ActiveRecord::Type.register(:downcased_string, DowncasedString)
175
144
 
176
- * Remove deprecated `ActiveModel::Errors#values`.
145
+ class User < ActiveRecord::Base
146
+ attribute :email, :downcased_string
147
+ end
177
148
 
178
- *Rafael Mendonça França*
149
+ user = User.new(email: "FooBar@example.com")
150
+ ```
179
151
 
180
- * Remove deprecated `ActiveModel::Errors#slice!`.
152
+ Serializing the `email` attribute for the database will be roughly twice as
153
+ fast. More expensive `cast` operations will likely see greater improvements.
181
154
 
182
- *Rafael Mendonça França*
155
+ *Jonathan Hefner*
183
156
 
184
- * Remove deprecated `ActiveModel::Errors#to_h`.
157
+ * `has_secure_password` now supports password challenges via a
158
+ `password_challenge` accessor and validation.
185
159
 
186
- *Rafael Mendonça França*
160
+ A password challenge is a safeguard to verify that the current user is
161
+ actually the password owner. It can be used when changing sensitive model
162
+ fields, such as the password itself. It is different than a password
163
+ confirmation, which is used to prevent password typos.
187
164
 
188
- * Remove deprecated enumeration of `ActiveModel::Errors` instances as a Hash.
165
+ When `password_challenge` is set, the validation checks that the value's
166
+ digest matches the *currently persisted* `password_digest` (i.e.
167
+ `password_digest_was`).
189
168
 
190
- *Rafael Mendonça França*
169
+ This allows a password challenge to be done as part of a typical `update`
170
+ call, just like a password confirmation. It also allows a password
171
+ challenge error to be handled in the same way as other validation errors.
191
172
 
192
- * Clear secure password cache if password is set to `nil`
173
+ For example, in the controller, instead of:
193
174
 
194
- Before:
175
+ ```ruby
176
+ password_params = params.require(:password).permit(
177
+ :password_challenge,
178
+ :password,
179
+ :password_confirmation,
180
+ )
195
181
 
196
- user.password = 'something'
197
- user.password = nil
182
+ password_challenge = password_params.delete(:password_challenge)
183
+ @password_challenge_failed = !current_user.authenticate(password_challenge)
198
184
 
199
- user.password # => 'something'
185
+ if !@password_challenge_failed && current_user.update(password_params)
186
+ # ...
187
+ end
188
+ ```
200
189
 
201
- Now:
190
+ You can now write:
202
191
 
203
- user.password = 'something'
204
- user.password = nil
192
+ ```ruby
193
+ password_params = params.require(:password).permit(
194
+ :password_challenge,
195
+ :password,
196
+ :password_confirmation,
197
+ ).with_defaults(password_challenge: "")
198
+
199
+ if current_user.update(password_params)
200
+ # ...
201
+ end
202
+ ```
205
203
 
206
- user.password # => nil
204
+ And, in the view, instead of checking `@password_challenge_failed`, you can
205
+ render an error for the `password_challenge` field just as you would for
206
+ other form fields, including utilizing `config.action_view.field_error_proc`.
207
207
 
208
- *Markus Doits*
208
+ *Jonathan Hefner*
209
209
 
210
- ## Rails 7.0.0.alpha2 (September 15, 2021) ##
210
+ * Support infinite ranges for `LengthValidator`s `:in`/`:within` options
211
211
 
212
- * No changes.
212
+ ```ruby
213
+ validates_length_of :first_name, in: ..30
214
+ ```
213
215
 
216
+ *fatkodima*
214
217
 
215
- ## Rails 7.0.0.alpha1 (September 15, 2021) ##
218
+ * Add support for beginless ranges to inclusivity/exclusivity validators:
216
219
 
217
- * Introduce `ActiveModel::API`.
220
+ ```ruby
221
+ validates_inclusion_of :birth_date, in: -> { (..Date.today) }
222
+ ```
218
223
 
219
- Make `ActiveModel::API` the minimum API to talk with Action Pack and Action View.
220
- This will allow adding more functionality to `ActiveModel::Model`.
224
+ ```ruby
225
+ validates_exclusion_of :birth_date, in: -> { (..Date.today) }
226
+ ```
221
227
 
222
- *Petrik de Heus*, *Nathaniel Watts*
228
+ *Bo Jeanes*
223
229
 
224
- * Fix dirty check for Float::NaN and BigDecimal::NaN.
230
+ * Make validators accept lambdas without record argument
225
231
 
226
- Float::NaN and BigDecimal::NaN in Ruby are [special values](https://bugs.ruby-lang.org/issues/1720)
227
- and can't be compared with `==`.
232
+ ```ruby
233
+ # Before
234
+ validates_comparison_of :birth_date, less_than_or_equal_to: ->(_record) { Date.today }
228
235
 
229
- *Marcelo Lauxen*
236
+ # After
237
+ validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
238
+ ```
230
239
 
231
- * Fix `to_json` for `ActiveModel::Dirty` object.
240
+ *fatkodima*
232
241
 
233
- Exclude `mutations_from_database` attribute from json as it lead to recursion.
242
+ * Fix casting long strings to `Date`, `Time` or `DateTime`
234
243
 
235
- *Anil Maurya*
244
+ *fatkodima*
236
245
 
237
- * Add `ActiveModel::AttributeSet#values_for_database`.
246
+ * Use different cache namespace for proxy calls
238
247
 
239
- Returns attributes with values for assignment to the database.
248
+ Models can currently have different attribute bodies for the same method
249
+ names, leading to conflicts. Adding a new namespace `:active_model_proxy`
250
+ fixes the issue.
240
251
 
241
252
  *Chris Salzberg*
242
253
 
243
- * Fix delegation in ActiveModel::Type::Registry#lookup and ActiveModel::Type.lookup.
244
-
245
- Passing a last positional argument `{}` would be incorrectly considered as keyword argument.
246
-
247
- *Benoit Daloze*
248
-
249
- * Cache and re-use generated attribute methods.
250
-
251
- Generated methods with identical implementations will now share their instruction sequences
252
- leading to reduced memory retention, and slightly faster load time.
253
-
254
- *Jean Boussier*
255
-
256
- * Add `in: range` parameter to `numericality` validator.
257
-
258
- *Michal Papis*
259
-
260
- * Add `locale` argument to `ActiveModel::Name#initialize` to be used to generate the `singular`,
261
- `plural`, `route_key` and `singular_route_key` values.
262
-
263
- *Lukas Pokorny*
264
-
265
- * Make ActiveModel::Errors#inspect slimmer for readability
266
-
267
- *lulalala*
268
-
269
- Please check [6-1-stable](https://github.com/rails/rails/blob/6-1-stable/activemodel/CHANGELOG.md) for previous changes.
254
+ 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:
@@ -10,7 +10,7 @@ module ActiveModel
10
10
  # keys matching the attribute names.
11
11
  #
12
12
  # If the passed hash responds to <tt>permitted?</tt> method and the return value
13
- # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
13
+ # of this method is +false+ an ActiveModel::ForbiddenAttributesError
14
14
  # exception is raised.
15
15
  #
16
16
  # class Cat