activemodel 7.2.3 → 8.0.5
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 +95 -39
- data/lib/active_model/attribute.rb +1 -1
- data/lib/active_model/attribute_assignment.rb +22 -1
- data/lib/active_model/attribute_methods.rb +2 -2
- data/lib/active_model/attribute_set.rb +1 -1
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +12 -5
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/secure_password.rb +44 -3
- data/lib/active_model/serialization.rb +21 -21
- data/lib/active_model/translation.rb +8 -3
- data/lib/active_model/type/helpers/time_value.rb +20 -44
- data/lib/active_model/type/value.rb +3 -3
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/validates.rb +7 -2
- data/lib/active_model/validations.rb +67 -30
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b99ed8ce4537620119305037cc30a84bfd6cb3ccf3f1b077d8d68d5be9ae7d2
|
|
4
|
+
data.tar.gz: 9aecd2c7cc55d8a7fb89ec50ee9b6cf1ac2550007f4d04144839de6580af9725
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5193de3e05b60947c2bd71d794d0ad10617a68364dcde3c35f3637933571290b85d49cdbb28f863a81e371253f90bdb6b7afddf1ce80ed18c3900deb7cee182c
|
|
7
|
+
data.tar.gz: 5b0dad9f7208135c074a0c10f8ea0be17642cf2d972796b40f09784997c5f24dbc12f9a1be8d25cced20bb0a83d600c86ee86e0cebb574433850a46c59c346ad
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
## Rails
|
|
1
|
+
## Rails 8.0.5 (March 24, 2026) ##
|
|
2
|
+
|
|
3
|
+
* No changes.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## Rails 8.0.4.1 (March 23, 2026) ##
|
|
7
|
+
|
|
8
|
+
* No changes.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Rails 8.0.4 (October 28, 2025) ##
|
|
12
|
+
|
|
13
|
+
* No changes.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Rails 8.0.3 (September 22, 2025) ##
|
|
2
17
|
|
|
3
18
|
* Fix `has_secure_password` to perform confirmation validation of the password even when blank.
|
|
4
19
|
|
|
@@ -6,83 +21,124 @@
|
|
|
6
21
|
|
|
7
22
|
*Fabio Sangiovanni*
|
|
8
23
|
|
|
9
|
-
* Handle missing attributes for `ActiveModel::Translation#human_attribute_name`.
|
|
10
24
|
|
|
11
|
-
|
|
25
|
+
## Rails 8.0.2.1 (August 13, 2025) ##
|
|
12
26
|
|
|
13
|
-
*
|
|
27
|
+
* No changes.
|
|
14
28
|
|
|
15
|
-
*Kouhei Yanagita*
|
|
16
29
|
|
|
30
|
+
## Rails 8.0.2 (March 12, 2025) ##
|
|
31
|
+
|
|
32
|
+
* No changes.
|
|
17
33
|
|
|
18
|
-
|
|
34
|
+
|
|
35
|
+
## Rails 8.0.1 (December 13, 2024) ##
|
|
19
36
|
|
|
20
37
|
* No changes.
|
|
21
38
|
|
|
22
39
|
|
|
23
|
-
## Rails
|
|
40
|
+
## Rails 8.0.0.1 (December 10, 2024) ##
|
|
24
41
|
|
|
25
42
|
* No changes.
|
|
26
43
|
|
|
27
44
|
|
|
28
|
-
## Rails
|
|
45
|
+
## Rails 8.0.0 (November 07, 2024) ##
|
|
29
46
|
|
|
30
|
-
*
|
|
47
|
+
* No changes.
|
|
31
48
|
|
|
32
|
-
`alias_attribute` would wrongly assume the attribute accessor was generated by Active Model.
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
class Person
|
|
36
|
-
include ActiveModel::AttributeMethods
|
|
50
|
+
## Rails 8.0.0.rc2 (October 30, 2024) ##
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
attr_accessor :name
|
|
52
|
+
* No changes.
|
|
40
53
|
|
|
41
|
-
|
|
54
|
+
|
|
55
|
+
## Rails 8.0.0.rc1 (October 19, 2024) ##
|
|
56
|
+
|
|
57
|
+
* Add `:except_on` option for validations. Grants the ability to _skip_ validations in specified contexts.
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
class User < ApplicationRecord
|
|
61
|
+
#...
|
|
62
|
+
validates :birthday, presence: { except_on: :admin }
|
|
63
|
+
#...
|
|
42
64
|
end
|
|
43
65
|
|
|
44
|
-
|
|
66
|
+
user = User.new(attributes except birthday)
|
|
67
|
+
user.save(context: :admin)
|
|
45
68
|
```
|
|
46
69
|
|
|
47
|
-
*
|
|
70
|
+
*Drew Bragg*
|
|
48
71
|
|
|
72
|
+
## Rails 8.0.0.beta1 (September 26, 2024) ##
|
|
49
73
|
|
|
50
|
-
|
|
74
|
+
* Make `ActiveModel::Serialization#read_attribute_for_serialization` public
|
|
51
75
|
|
|
52
|
-
*
|
|
76
|
+
*Sean Doyle*
|
|
53
77
|
|
|
78
|
+
* Add a default token generator for password reset tokens when using `has_secure_password`.
|
|
54
79
|
|
|
55
|
-
|
|
80
|
+
```ruby
|
|
81
|
+
class User < ApplicationRecord
|
|
82
|
+
has_secure_password
|
|
83
|
+
end
|
|
56
84
|
|
|
57
|
-
|
|
85
|
+
user = User.create!(name: "david", password: "123", password_confirmation: "123")
|
|
86
|
+
token = user.password_reset_token
|
|
87
|
+
User.find_by_password_reset_token(token) # returns user
|
|
88
|
+
|
|
89
|
+
# 16 minutes later...
|
|
90
|
+
User.find_by_password_reset_token(token) # returns nil
|
|
58
91
|
|
|
92
|
+
# raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired
|
|
93
|
+
User.find_by_password_reset_token!(token)
|
|
94
|
+
```
|
|
59
95
|
|
|
60
|
-
|
|
96
|
+
*DHH*
|
|
61
97
|
|
|
62
|
-
*
|
|
98
|
+
* Add a load hook `active_model_translation` for `ActiveModel::Translation`.
|
|
63
99
|
|
|
100
|
+
*Shouichi Kamiya*
|
|
64
101
|
|
|
65
|
-
|
|
102
|
+
* Add `raise_on_missing_translations` option to `ActiveModel::Translation`.
|
|
103
|
+
When the option is set, `human_attribute_name` raises an error if a translation of the given attribute is missing.
|
|
66
104
|
|
|
67
|
-
|
|
68
|
-
|
|
105
|
+
```ruby
|
|
106
|
+
# ActiveModel::Translation.raise_on_missing_translations = false
|
|
107
|
+
Post.human_attribute_name("title")
|
|
108
|
+
=> "Title"
|
|
109
|
+
|
|
110
|
+
# ActiveModel::Translation.raise_on_missing_translations = true
|
|
111
|
+
Post.human_attribute_name("title")
|
|
112
|
+
=> Translation missing. Options considered were: (I18n::MissingTranslationData)
|
|
113
|
+
- en.activerecord.attributes.post.title
|
|
114
|
+
- en.attributes.title
|
|
115
|
+
|
|
116
|
+
raise exception.respond_to?(:to_exception) ? exception.to_exception : exception
|
|
117
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
118
|
+
```
|
|
69
119
|
|
|
70
|
-
*
|
|
120
|
+
*Shouichi Kamiya*
|
|
71
121
|
|
|
72
|
-
*
|
|
73
|
-
`ActiveModel::Attributes` will now provide this method. This method behaves
|
|
74
|
-
the same for Active Model as it does for Active Record.
|
|
122
|
+
* Introduce `ActiveModel::AttributeAssignment#attribute_writer_missing`
|
|
75
123
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
include ActiveModel::Attributes
|
|
124
|
+
Provide instances with an opportunity to gracefully handle assigning to an
|
|
125
|
+
unknown attribute:
|
|
79
126
|
|
|
80
|
-
|
|
127
|
+
```ruby
|
|
128
|
+
class Rectangle
|
|
129
|
+
include ActiveModel::AttributeAssignment
|
|
130
|
+
|
|
131
|
+
attr_accessor :length, :width
|
|
132
|
+
|
|
133
|
+
def attribute_writer_missing(name, value)
|
|
134
|
+
Rails.logger.warn "Tried to assign to unknown attribute #{name}"
|
|
81
135
|
end
|
|
136
|
+
end
|
|
82
137
|
|
|
83
|
-
|
|
84
|
-
|
|
138
|
+
rectangle = Rectangle.new
|
|
139
|
+
rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"
|
|
140
|
+
```
|
|
85
141
|
|
|
86
|
-
*
|
|
142
|
+
*Sean Doyle*
|
|
87
143
|
|
|
88
|
-
Please check [7-
|
|
144
|
+
Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activemodel/CHANGELOG.md) for previous changes.
|
|
@@ -36,6 +36,27 @@ 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
62
|
attributes.each_pair do |k, v|
|
|
@@ -50,7 +71,7 @@ module ActiveModel
|
|
|
50
71
|
if respond_to?(setter)
|
|
51
72
|
raise
|
|
52
73
|
else
|
|
53
|
-
|
|
74
|
+
attribute_writer_missing(k.to_s, v)
|
|
54
75
|
end
|
|
55
76
|
end
|
|
56
77
|
end
|
|
@@ -321,8 +321,8 @@ module ActiveModel
|
|
|
321
321
|
canonical_method_name = pattern.method_name(attr_name)
|
|
322
322
|
public_method_name = pattern.method_name(as)
|
|
323
323
|
|
|
324
|
-
# If defining a regular attribute method, we don't override methods that are
|
|
325
|
-
# defined in
|
|
324
|
+
# If defining a regular attribute method, we don't override methods that are explicitly
|
|
325
|
+
# defined in parent classes.
|
|
326
326
|
if instance_method_already_implemented?(public_method_name)
|
|
327
327
|
# However, for `alias_attribute`, we always define the method.
|
|
328
328
|
# We check for override second because `instance_method_already_implemented?`
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module ActiveModel
|
|
4
4
|
# = Active \Model \Conversion
|
|
5
5
|
#
|
|
6
|
-
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
|
|
6
|
+
# Handles default conversions: #to_model, #to_key, #to_param, and #to_partial_path.
|
|
7
7
|
#
|
|
8
8
|
# Let's take for example this non-persisted object.
|
|
9
9
|
#
|
data/lib/active_model/dirty.rb
CHANGED
|
@@ -247,16 +247,23 @@ module ActiveModel
|
|
|
247
247
|
|
|
248
248
|
def initialize_dup(other) # :nodoc:
|
|
249
249
|
super
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
@mutations_from_database = nil
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def init_attributes(other) # :nodoc:
|
|
254
|
+
attrs = super
|
|
255
|
+
if other.persisted? && self.class.respond_to?(:_default_attributes)
|
|
256
|
+
self.class._default_attributes.map do |attr|
|
|
257
|
+
attr.with_value_from_user(attrs.fetch_value(attr.name))
|
|
253
258
|
end
|
|
259
|
+
else
|
|
260
|
+
attrs
|
|
254
261
|
end
|
|
255
|
-
@mutations_from_database = nil
|
|
256
262
|
end
|
|
257
263
|
|
|
258
264
|
def as_json(options = {}) # :nodoc:
|
|
259
|
-
|
|
265
|
+
except = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
|
|
266
|
+
options = options.merge except: except
|
|
260
267
|
super(options)
|
|
261
268
|
end
|
|
262
269
|
|
|
@@ -39,6 +39,10 @@ module ActiveModel
|
|
|
39
39
|
# <tt>validations: false</tt> as an argument. This allows complete
|
|
40
40
|
# customizability of validation behavior.
|
|
41
41
|
#
|
|
42
|
+
# Finally, a password reset token that's valid for 15 minutes after issue
|
|
43
|
+
# is automatically configured when +reset_token+ is set to true (which it is by default)
|
|
44
|
+
# and the object responds to +generates_token_for+ (which Active Records do).
|
|
45
|
+
#
|
|
42
46
|
# To use +has_secure_password+, add bcrypt (~> 3.1.7) to your Gemfile:
|
|
43
47
|
#
|
|
44
48
|
# gem "bcrypt", "~> 3.1.7"
|
|
@@ -98,7 +102,18 @@ module ActiveModel
|
|
|
98
102
|
# account.is_guest = true
|
|
99
103
|
# account.valid? # => true
|
|
100
104
|
#
|
|
101
|
-
|
|
105
|
+
# ===== Using the password reset token
|
|
106
|
+
#
|
|
107
|
+
# user = User.create!(name: "david", password: "123", password_confirmation: "123")
|
|
108
|
+
# token = user.password_reset_token
|
|
109
|
+
# User.find_by_password_reset_token(token) # returns user
|
|
110
|
+
#
|
|
111
|
+
# # 16 minutes later...
|
|
112
|
+
# User.find_by_password_reset_token(token) # returns nil
|
|
113
|
+
#
|
|
114
|
+
# # raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired
|
|
115
|
+
# User.find_by_password_reset_token!(token)
|
|
116
|
+
def has_secure_password(attribute = :password, validations: true, reset_token: true)
|
|
102
117
|
# Load bcrypt gem only when has_secure_password is used.
|
|
103
118
|
# This is to avoid ActiveModel (and by extension the entire framework)
|
|
104
119
|
# being dependent on a binary library.
|
|
@@ -109,7 +124,7 @@ module ActiveModel
|
|
|
109
124
|
raise
|
|
110
125
|
end
|
|
111
126
|
|
|
112
|
-
include InstanceMethodsOnActivation.new(attribute)
|
|
127
|
+
include InstanceMethodsOnActivation.new(attribute, reset_token: reset_token)
|
|
113
128
|
|
|
114
129
|
if validations
|
|
115
130
|
include ActiveModel::Validations
|
|
@@ -142,11 +157,30 @@ module ActiveModel
|
|
|
142
157
|
|
|
143
158
|
validates_confirmation_of attribute, allow_nil: true
|
|
144
159
|
end
|
|
160
|
+
|
|
161
|
+
# Only generate tokens for records that are capable of doing so (Active Records, not vanilla Active Models)
|
|
162
|
+
if reset_token && respond_to?(:generates_token_for)
|
|
163
|
+
generates_token_for :"#{attribute}_reset", expires_in: 15.minutes do
|
|
164
|
+
public_send(:"#{attribute}_salt")&.last(10)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
168
|
+
silence_redefinition_of_method :find_by_#{attribute}_reset_token
|
|
169
|
+
def self.find_by_#{attribute}_reset_token(token)
|
|
170
|
+
find_by_token_for(:#{attribute}_reset, token)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
silence_redefinition_of_method :find_by_#{attribute}_reset_token!
|
|
174
|
+
def self.find_by_#{attribute}_reset_token!(token)
|
|
175
|
+
find_by_token_for!(:#{attribute}_reset, token)
|
|
176
|
+
end
|
|
177
|
+
RUBY
|
|
178
|
+
end
|
|
145
179
|
end
|
|
146
180
|
end
|
|
147
181
|
|
|
148
182
|
class InstanceMethodsOnActivation < Module
|
|
149
|
-
def initialize(attribute)
|
|
183
|
+
def initialize(attribute, reset_token:)
|
|
150
184
|
attr_reader attribute
|
|
151
185
|
|
|
152
186
|
define_method("#{attribute}=") do |unencrypted_password|
|
|
@@ -184,6 +218,13 @@ module ActiveModel
|
|
|
184
218
|
end
|
|
185
219
|
|
|
186
220
|
alias_method :authenticate, :authenticate_password if attribute == :password
|
|
221
|
+
|
|
222
|
+
if reset_token
|
|
223
|
+
# Returns the class-level configured reset token for the password.
|
|
224
|
+
define_method("#{attribute}_reset_token") do
|
|
225
|
+
generate_token_for(:"#{attribute}_reset")
|
|
226
|
+
end
|
|
227
|
+
end
|
|
187
228
|
end
|
|
188
229
|
end
|
|
189
230
|
end
|
|
@@ -29,8 +29,8 @@ module ActiveModel
|
|
|
29
29
|
# An +attributes+ hash must be defined and should contain any attributes you
|
|
30
30
|
# need to be serialized. Attributes must be strings, not symbols.
|
|
31
31
|
# When called, serializable hash will use instance methods that match the name
|
|
32
|
-
# of the attributes hash's keys. In order to override this behavior,
|
|
33
|
-
#
|
|
32
|
+
# of the attributes hash's keys. In order to override this behavior, override
|
|
33
|
+
# the +read_attribute_for_serialization+ method.
|
|
34
34
|
#
|
|
35
35
|
# ActiveModel::Serializers::JSON module automatically includes
|
|
36
36
|
# the +ActiveModel::Serialization+ module, so there is no need to
|
|
@@ -128,7 +128,7 @@ module ActiveModel
|
|
|
128
128
|
return serializable_attributes(attribute_names) if options.blank?
|
|
129
129
|
|
|
130
130
|
if only = options[:only]
|
|
131
|
-
attribute_names
|
|
131
|
+
attribute_names = Array(only).map(&:to_s) & attribute_names
|
|
132
132
|
elsif except = options[:except]
|
|
133
133
|
attribute_names -= Array(except).map(&:to_s)
|
|
134
134
|
end
|
|
@@ -148,29 +148,29 @@ module ActiveModel
|
|
|
148
148
|
hash
|
|
149
149
|
end
|
|
150
150
|
|
|
151
|
+
# Hook method defining how an attribute value should be retrieved for
|
|
152
|
+
# serialization. By default this is assumed to be an instance named after
|
|
153
|
+
# the attribute. Override this method in subclasses should you need to
|
|
154
|
+
# retrieve the value for a given attribute differently:
|
|
155
|
+
#
|
|
156
|
+
# class MyClass
|
|
157
|
+
# include ActiveModel::Serialization
|
|
158
|
+
#
|
|
159
|
+
# def initialize(data = {})
|
|
160
|
+
# @data = data
|
|
161
|
+
# end
|
|
162
|
+
#
|
|
163
|
+
# def read_attribute_for_serialization(key)
|
|
164
|
+
# @data[key]
|
|
165
|
+
# end
|
|
166
|
+
# end
|
|
167
|
+
alias :read_attribute_for_serialization :send
|
|
168
|
+
|
|
151
169
|
private
|
|
152
170
|
def attribute_names_for_serialization
|
|
153
171
|
attributes.keys
|
|
154
172
|
end
|
|
155
173
|
|
|
156
|
-
# Hook method defining how an attribute value should be retrieved for
|
|
157
|
-
# serialization. By default this is assumed to be an instance named after
|
|
158
|
-
# the attribute. Override this method in subclasses should you need to
|
|
159
|
-
# retrieve the value for a given attribute differently:
|
|
160
|
-
#
|
|
161
|
-
# class MyClass
|
|
162
|
-
# include ActiveModel::Serialization
|
|
163
|
-
#
|
|
164
|
-
# def initialize(data = {})
|
|
165
|
-
# @data = data
|
|
166
|
-
# end
|
|
167
|
-
#
|
|
168
|
-
# def read_attribute_for_serialization(key)
|
|
169
|
-
# @data[key]
|
|
170
|
-
# end
|
|
171
|
-
# end
|
|
172
|
-
alias :read_attribute_for_serialization :send
|
|
173
|
-
|
|
174
174
|
def serializable_attributes(attribute_names)
|
|
175
175
|
attribute_names.index_with { |n| read_attribute_for_serialization(n) }
|
|
176
176
|
end
|
|
@@ -22,6 +22,8 @@ module ActiveModel
|
|
|
22
22
|
module Translation
|
|
23
23
|
include ActiveModel::Naming
|
|
24
24
|
|
|
25
|
+
singleton_class.attr_accessor :raise_on_missing_translations
|
|
26
|
+
|
|
25
27
|
# Returns the +i18n_scope+ for the class. Override if you want custom lookup.
|
|
26
28
|
def i18n_scope
|
|
27
29
|
:activemodel
|
|
@@ -69,16 +71,19 @@ module ActiveModel
|
|
|
69
71
|
end
|
|
70
72
|
end
|
|
71
73
|
|
|
74
|
+
raise_on_missing = options.fetch(:raise, Translation.raise_on_missing_translations)
|
|
75
|
+
|
|
72
76
|
defaults << :"attributes.#{attribute}"
|
|
73
77
|
defaults << options[:default] if options[:default]
|
|
74
|
-
defaults << MISSING_TRANSLATION
|
|
78
|
+
defaults << MISSING_TRANSLATION unless raise_on_missing
|
|
75
79
|
|
|
76
|
-
translation = I18n.translate(defaults.shift, count: 1, **options, default: defaults)
|
|
80
|
+
translation = I18n.translate(defaults.shift, count: 1, raise: raise_on_missing, **options, default: defaults)
|
|
77
81
|
if translation == MISSING_TRANSLATION
|
|
78
82
|
translation = attribute.present? ? attribute.humanize : namespace.humanize
|
|
79
83
|
end
|
|
80
|
-
|
|
81
84
|
translation
|
|
82
85
|
end
|
|
83
86
|
end
|
|
87
|
+
|
|
88
|
+
ActiveSupport.run_load_hooks(:active_model_translation, Translation)
|
|
84
89
|
end
|
|
@@ -69,56 +69,32 @@ module ActiveModel
|
|
|
69
69
|
\z
|
|
70
70
|
/x
|
|
71
71
|
|
|
72
|
-
if
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
::Time.new(string)
|
|
84
|
-
end
|
|
85
|
-
rescue ArgumentError
|
|
86
|
-
nil
|
|
87
|
-
end
|
|
88
|
-
else
|
|
89
|
-
def fast_string_to_time(string)
|
|
90
|
-
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
|
91
|
-
|
|
92
|
-
if is_utc?
|
|
93
|
-
::Time.new(string, in: "UTC")
|
|
94
|
-
else
|
|
95
|
-
::Time.new(string)
|
|
96
|
-
end
|
|
97
|
-
rescue ArgumentError
|
|
98
|
-
nil
|
|
72
|
+
if Time.new(2000, 1, 1, 0, 0, 0, "-00:00").yday != 1 # Early 3.2.x had a bug
|
|
73
|
+
# BUG: Wrapping the Time object with Time.at because Time.new with `in:` in Ruby 3.2.0
|
|
74
|
+
# used to return an invalid Time object
|
|
75
|
+
# see: https://bugs.ruby-lang.org/issues/19292
|
|
76
|
+
def fast_string_to_time(string)
|
|
77
|
+
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
|
78
|
+
|
|
79
|
+
if is_utc?
|
|
80
|
+
::Time.at(::Time.new(string, in: "UTC"))
|
|
81
|
+
else
|
|
82
|
+
::Time.new(string)
|
|
99
83
|
end
|
|
84
|
+
rescue ArgumentError
|
|
85
|
+
nil
|
|
100
86
|
end
|
|
101
87
|
else
|
|
102
88
|
def fast_string_to_time(string)
|
|
103
|
-
return unless
|
|
89
|
+
return unless string.include?("-") # Time.new("1234") # => 1234-01-01 00:00:00
|
|
104
90
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
if is_utc?
|
|
92
|
+
::Time.new(string, in: "UTC")
|
|
93
|
+
else
|
|
94
|
+
::Time.new(string)
|
|
109
95
|
end
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
offset = \
|
|
113
|
-
if $8 == "Z"
|
|
114
|
-
0
|
|
115
|
-
else
|
|
116
|
-
offset_h, offset_m = $8.to_i, $9.to_i
|
|
117
|
-
offset_h.to_i * 3600 + (offset_h.negative? ? -1 : 1) * offset_m * 60
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
|
|
96
|
+
rescue ArgumentError
|
|
97
|
+
nil
|
|
122
98
|
end
|
|
123
99
|
end
|
|
124
100
|
end
|
|
@@ -25,7 +25,7 @@ module ActiveModel
|
|
|
25
25
|
# by the database. For example a boolean type can return +true+ if the
|
|
26
26
|
# value parameter is a Ruby boolean, but may return +false+ if the value
|
|
27
27
|
# parameter is some other object.
|
|
28
|
-
def serializable?(value)
|
|
28
|
+
def serializable?(value, &_)
|
|
29
29
|
true
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -114,8 +114,8 @@ module ActiveModel
|
|
|
114
114
|
false
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
def map(value) # :nodoc:
|
|
118
|
-
|
|
117
|
+
def map(value, &) # :nodoc:
|
|
118
|
+
value
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
def ==(other)
|
|
@@ -78,7 +78,12 @@ module ActiveModel
|
|
|
78
78
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
|
79
79
|
# <tt>on: :custom_validation_context</tt> or
|
|
80
80
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
|
81
|
-
# * <tt>:
|
|
81
|
+
# * <tt>:except_on</tt> - Specifies the contexts where this validation is not active.
|
|
82
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
|
83
|
+
# or an array of symbols. (e.g. <tt>except: :create</tt> or
|
|
84
|
+
# <tt>except_on: :custom_validation_context</tt> or
|
|
85
|
+
# <tt>except_on: [:create, :custom_validation_context]</tt>)
|
|
86
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
|
82
87
|
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
|
83
88
|
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
|
84
89
|
# proc or string should return or evaluate to a +true+ or +false+ value.
|
|
@@ -155,7 +160,7 @@ module ActiveModel
|
|
|
155
160
|
# When creating custom validators, it might be useful to be able to specify
|
|
156
161
|
# additional default keys. This can be done by overwriting this method.
|
|
157
162
|
def _validates_default_keys
|
|
158
|
-
[:if, :unless, :on, :allow_blank, :allow_nil, :strict]
|
|
163
|
+
[:if, :unless, :on, :allow_blank, :allow_nil, :strict, :except_on]
|
|
159
164
|
end
|
|
160
165
|
|
|
161
166
|
def _parse_validates_options(options)
|
|
@@ -45,27 +45,6 @@ module ActiveModel
|
|
|
45
45
|
extend HelperMethods
|
|
46
46
|
include HelperMethods
|
|
47
47
|
|
|
48
|
-
##
|
|
49
|
-
# :method: validation_context
|
|
50
|
-
# Returns the context when running validations.
|
|
51
|
-
#
|
|
52
|
-
# This is useful when running validations except a certain context (opposite to the +on+ option).
|
|
53
|
-
#
|
|
54
|
-
# class Person
|
|
55
|
-
# include ActiveModel::Validations
|
|
56
|
-
#
|
|
57
|
-
# attr_accessor :name
|
|
58
|
-
# validates :name, presence: true, if: -> { validation_context != :custom }
|
|
59
|
-
# end
|
|
60
|
-
#
|
|
61
|
-
# person = Person.new
|
|
62
|
-
# person.valid? #=> false
|
|
63
|
-
# person.valid?(:new) #=> false
|
|
64
|
-
# person.valid?(:custom) #=> true
|
|
65
|
-
|
|
66
|
-
##
|
|
67
|
-
attr_accessor :validation_context
|
|
68
|
-
private :validation_context=
|
|
69
48
|
define_callbacks :validate, scope: :name
|
|
70
49
|
|
|
71
50
|
class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] }
|
|
@@ -91,6 +70,11 @@ module ActiveModel
|
|
|
91
70
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
|
92
71
|
# <tt>on: :custom_validation_context</tt> or
|
|
93
72
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
|
73
|
+
# * <tt>:except_on</tt> - Specifies the contexts where this validation is not active.
|
|
74
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
|
75
|
+
# or an array of symbols. (e.g. <tt>except: :create</tt> or
|
|
76
|
+
# <tt>except_on: :custom_validation_context</tt> or
|
|
77
|
+
# <tt>except_on: [:create, :custom_validation_context]</tt>)
|
|
94
78
|
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
|
|
95
79
|
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
|
|
96
80
|
# * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
|
|
@@ -106,7 +90,7 @@ module ActiveModel
|
|
|
106
90
|
validates_with BlockValidator, _merge_attributes(attr_names), &block
|
|
107
91
|
end
|
|
108
92
|
|
|
109
|
-
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
|
|
93
|
+
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend, :except_on].freeze # :nodoc:
|
|
110
94
|
|
|
111
95
|
# Adds a validation method or block to the class. This is useful when
|
|
112
96
|
# overriding the +validate+ instance method becomes too unwieldy and
|
|
@@ -158,14 +142,19 @@ module ActiveModel
|
|
|
158
142
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
|
159
143
|
# <tt>on: :custom_validation_context</tt> or
|
|
160
144
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
|
161
|
-
# * <tt>:
|
|
145
|
+
# * <tt>:except_on</tt> - Specifies the contexts where this validation is not active.
|
|
146
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
|
147
|
+
# or an array of symbols. (e.g. <tt>except: :create</tt> or
|
|
148
|
+
# <tt>except_on: :custom_validation_context</tt> or
|
|
149
|
+
# <tt>except_on: [:create, :custom_validation_context]</tt>)
|
|
150
|
+
# * <tt>:if</tt> - Specifies a method or proc to call to determine
|
|
162
151
|
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
|
163
|
-
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method
|
|
164
|
-
# proc
|
|
165
|
-
# * <tt>:unless</tt> - Specifies a method
|
|
152
|
+
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method or
|
|
153
|
+
# proc should return or evaluate to a +true+ or +false+ value.
|
|
154
|
+
# * <tt>:unless</tt> - Specifies a method or proc to call to
|
|
166
155
|
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
|
|
167
156
|
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
|
168
|
-
# method
|
|
157
|
+
# method or proc should return or evaluate to a +true+ or +false+
|
|
169
158
|
# value.
|
|
170
159
|
#
|
|
171
160
|
# NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
|
|
@@ -185,6 +174,15 @@ module ActiveModel
|
|
|
185
174
|
options = options.merge(if: [predicate_for_validation_context(options[:on]), *options[:if]])
|
|
186
175
|
end
|
|
187
176
|
|
|
177
|
+
if options.key?(:except_on)
|
|
178
|
+
options = options.dup
|
|
179
|
+
options[:except_on] = Array(options[:except_on])
|
|
180
|
+
options[:unless] = [
|
|
181
|
+
->(o) { options[:except_on].intersect?(Array(o.validation_context)) },
|
|
182
|
+
*options[:unless]
|
|
183
|
+
]
|
|
184
|
+
end
|
|
185
|
+
|
|
188
186
|
set_callback(:validate, *args, options, &block)
|
|
189
187
|
end
|
|
190
188
|
|
|
@@ -363,15 +361,23 @@ module ActiveModel
|
|
|
363
361
|
# person.valid? # => true
|
|
364
362
|
# person.valid?(:new) # => false
|
|
365
363
|
def valid?(context = nil)
|
|
366
|
-
current_context
|
|
364
|
+
current_context = validation_context
|
|
365
|
+
context_for_validation.context = context
|
|
367
366
|
errors.clear
|
|
368
367
|
run_validations!
|
|
369
368
|
ensure
|
|
370
|
-
|
|
369
|
+
context_for_validation.context = current_context
|
|
371
370
|
end
|
|
372
371
|
|
|
373
372
|
alias_method :validate, :valid?
|
|
374
373
|
|
|
374
|
+
def freeze
|
|
375
|
+
errors
|
|
376
|
+
context_for_validation
|
|
377
|
+
|
|
378
|
+
super
|
|
379
|
+
end
|
|
380
|
+
|
|
375
381
|
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
|
|
376
382
|
# added, +false+ otherwise.
|
|
377
383
|
#
|
|
@@ -432,11 +438,38 @@ module ActiveModel
|
|
|
432
438
|
# end
|
|
433
439
|
alias :read_attribute_for_validation :send
|
|
434
440
|
|
|
441
|
+
# Returns the context when running validations.
|
|
442
|
+
#
|
|
443
|
+
# This is useful when running validations except a certain context (opposite to the +on+ option).
|
|
444
|
+
#
|
|
445
|
+
# class Person
|
|
446
|
+
# include ActiveModel::Validations
|
|
447
|
+
#
|
|
448
|
+
# attr_accessor :name
|
|
449
|
+
# validates :name, presence: true, if: -> { validation_context != :custom }
|
|
450
|
+
# end
|
|
451
|
+
#
|
|
452
|
+
# person = Person.new
|
|
453
|
+
# person.valid? #=> false
|
|
454
|
+
# person.valid?(:new) #=> false
|
|
455
|
+
# person.valid?(:custom) #=> true
|
|
456
|
+
def validation_context
|
|
457
|
+
context_for_validation.context
|
|
458
|
+
end
|
|
459
|
+
|
|
435
460
|
private
|
|
461
|
+
def validation_context=(context)
|
|
462
|
+
context_for_validation.context = context
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def context_for_validation
|
|
466
|
+
@context_for_validation ||= ValidationContext.new
|
|
467
|
+
end
|
|
468
|
+
|
|
436
469
|
def init_internals
|
|
437
470
|
super
|
|
438
471
|
@errors = nil
|
|
439
|
-
@
|
|
472
|
+
@context_for_validation = nil
|
|
440
473
|
end
|
|
441
474
|
|
|
442
475
|
def run_validations!
|
|
@@ -468,6 +501,10 @@ module ActiveModel
|
|
|
468
501
|
super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
|
|
469
502
|
end
|
|
470
503
|
end
|
|
504
|
+
|
|
505
|
+
class ValidationContext # :nodoc:
|
|
506
|
+
attr_accessor :context
|
|
507
|
+
end
|
|
471
508
|
end
|
|
472
509
|
|
|
473
510
|
Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
|
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:
|
|
4
|
+
version: 8.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 8.0.5
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 8.0.5
|
|
26
26
|
description: A toolkit for building modeling frameworks like Active Record. Rich support
|
|
27
27
|
for attributes, callbacks, validations, serialization, internationalization, and
|
|
28
28
|
testing.
|
|
@@ -111,10 +111,10 @@ licenses:
|
|
|
111
111
|
- MIT
|
|
112
112
|
metadata:
|
|
113
113
|
bug_tracker_uri: https://github.com/rails/rails/issues
|
|
114
|
-
changelog_uri: https://github.com/rails/rails/blob/
|
|
115
|
-
documentation_uri: https://api.rubyonrails.org/
|
|
114
|
+
changelog_uri: https://github.com/rails/rails/blob/v8.0.5/activemodel/CHANGELOG.md
|
|
115
|
+
documentation_uri: https://api.rubyonrails.org/v8.0.5/
|
|
116
116
|
mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
|
|
117
|
-
source_code_uri: https://github.com/rails/rails/tree/
|
|
117
|
+
source_code_uri: https://github.com/rails/rails/tree/v8.0.5/activemodel
|
|
118
118
|
rubygems_mfa_required: 'true'
|
|
119
119
|
rdoc_options: []
|
|
120
120
|
require_paths:
|
|
@@ -123,14 +123,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
123
123
|
requirements:
|
|
124
124
|
- - ">="
|
|
125
125
|
- !ruby/object:Gem::Version
|
|
126
|
-
version: 3.
|
|
126
|
+
version: 3.2.0
|
|
127
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
129
|
- - ">="
|
|
130
130
|
- !ruby/object:Gem::Version
|
|
131
131
|
version: '0'
|
|
132
132
|
requirements: []
|
|
133
|
-
rubygems_version:
|
|
133
|
+
rubygems_version: 4.0.6
|
|
134
134
|
specification_version: 4
|
|
135
135
|
summary: A toolkit for building modeling frameworks (part of Rails).
|
|
136
136
|
test_files: []
|