activemodel 7.2.1.1 → 8.0.0.rc1
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 +75 -19
- data/lib/active_model/attribute_assignment.rb +22 -1
- data/lib/active_model/attribute_methods.rb +8 -3
- 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 +4 -4
- data/lib/active_model/secure_password.rb +44 -3
- data/lib/active_model/serialization.rb +21 -21
- data/lib/active_model/translation.rb +8 -2
- data/lib/active_model/type/helpers/time_value.rb +20 -44
- data/lib/active_model/type/value.rb +2 -2
- data/lib/active_model/validations/validates.rb +7 -2
- data/lib/active_model/validations.rb +63 -26
- 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: cff9ce182fe19b1ba44acb21c565c8527602855ad42582c68ca865c6c7808418
|
4
|
+
data.tar.gz: ac9f1b90cdad91c42d192ccd31fff45f96236a05dfab084a6846a1bba2881f31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1cd9e0aabc319cfa3b73d3951470b2c31adedc2aafc11a5cd25a13ddaa7f346db103d6351009bd0f681184e8fbf840358df747b808e59e2857133bd6e372561
|
7
|
+
data.tar.gz: 917d12bda8d9f7f7f442897256fd046d27f29ca0f8a28aee17c68773cf48772f0142f30603af455f952ec1a308a20b4fc70367eba3dcabaa28cb1c24397eccef
|
data/CHANGELOG.md
CHANGED
@@ -1,34 +1,90 @@
|
|
1
|
-
## Rails
|
1
|
+
## Rails 8.0.0.rc1 (October 19, 2024) ##
|
2
2
|
|
3
|
-
*
|
3
|
+
* Add `:except_on` option for validations. Grants the ability to _skip_ validations in specified contexts.
|
4
4
|
|
5
|
+
```ruby
|
6
|
+
class User < ApplicationRecord
|
7
|
+
#...
|
8
|
+
validates :birthday, presence: { except_on: :admin }
|
9
|
+
#...
|
10
|
+
end
|
5
11
|
|
6
|
-
|
12
|
+
user = User.new(attributes except birthday)
|
13
|
+
user.save(context: :admin)
|
14
|
+
```
|
7
15
|
|
8
|
-
*
|
16
|
+
*Drew Bragg*
|
9
17
|
|
18
|
+
## Rails 8.0.0.beta1 (September 26, 2024) ##
|
10
19
|
|
11
|
-
|
20
|
+
* Make `ActiveModel::Serialization#read_attribute_for_serialization` public
|
12
21
|
|
13
|
-
*
|
14
|
-
calculate minus minute value in TZ offset correctly.
|
22
|
+
*Sean Doyle*
|
15
23
|
|
16
|
-
|
24
|
+
* Add a default token generator for password reset tokens when using `has_secure_password`.
|
17
25
|
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
```ruby
|
27
|
+
class User < ApplicationRecord
|
28
|
+
has_secure_password
|
29
|
+
end
|
21
30
|
|
22
|
-
|
23
|
-
|
24
|
-
|
31
|
+
user = User.create!(name: "david", password: "123", password_confirmation: "123")
|
32
|
+
token = user.password_reset_token
|
33
|
+
User.find_by_password_reset_token(token) # returns user
|
25
34
|
|
26
|
-
|
35
|
+
# 16 minutes later...
|
36
|
+
User.find_by_password_reset_token(token) # returns nil
|
37
|
+
|
38
|
+
# raises ActiveSupport::MessageVerifier::InvalidSignature since the token is expired
|
39
|
+
User.find_by_password_reset_token!(token)
|
40
|
+
```
|
41
|
+
|
42
|
+
*DHH*
|
43
|
+
|
44
|
+
* Add a load hook `active_model_translation` for `ActiveModel::Translation`.
|
45
|
+
|
46
|
+
*Shouichi Kamiya*
|
47
|
+
|
48
|
+
* Add `raise_on_missing_translations` option to `ActiveModel::Translation`.
|
49
|
+
When the option is set, `human_attribute_name` raises an error if a translation of the given attribute is missing.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# ActiveModel::Translation.raise_on_missing_translations = false
|
53
|
+
Post.human_attribute_name("title")
|
54
|
+
=> "Title"
|
55
|
+
|
56
|
+
# ActiveModel::Translation.raise_on_missing_translations = true
|
57
|
+
Post.human_attribute_name("title")
|
58
|
+
=> Translation missing. Options considered were: (I18n::MissingTranslationData)
|
59
|
+
- en.activerecord.attributes.post.title
|
60
|
+
- en.attributes.title
|
61
|
+
|
62
|
+
raise exception.respond_to?(:to_exception) ? exception.to_exception : exception
|
63
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
64
|
+
```
|
65
|
+
|
66
|
+
*Shouichi Kamiya*
|
67
|
+
|
68
|
+
* Introduce `ActiveModel::AttributeAssignment#attribute_writer_missing`
|
69
|
+
|
70
|
+
Provide instances with an opportunity to gracefully handle assigning to an
|
71
|
+
unknown attribute:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class Rectangle
|
75
|
+
include ActiveModel::AttributeAssignment
|
76
|
+
|
77
|
+
attr_accessor :length, :width
|
78
|
+
|
79
|
+
def attribute_writer_missing(name, value)
|
80
|
+
Rails.logger.warn "Tried to assign to unknown attribute #{name}"
|
27
81
|
end
|
82
|
+
end
|
28
83
|
|
29
|
-
|
30
|
-
|
84
|
+
rectangle = Rectangle.new
|
85
|
+
rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"
|
86
|
+
```
|
31
87
|
|
32
|
-
*
|
88
|
+
*Sean Doyle*
|
33
89
|
|
34
|
-
Please check [7-
|
90
|
+
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 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
|
@@ -215,7 +215,12 @@ module ActiveModel
|
|
215
215
|
end
|
216
216
|
|
217
217
|
def generate_alias_attribute_methods(code_generator, new_name, old_name)
|
218
|
-
|
218
|
+
ActiveSupport::CodeGenerator.batch(code_generator, __FILE__, __LINE__) do |owner|
|
219
|
+
attribute_method_patterns.each do |pattern|
|
220
|
+
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
221
|
+
end
|
222
|
+
attribute_method_patterns_cache.clear
|
223
|
+
end
|
219
224
|
end
|
220
225
|
|
221
226
|
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
|
@@ -228,7 +233,7 @@ module ActiveModel
|
|
228
233
|
call_args = []
|
229
234
|
call_args << parameters if parameters
|
230
235
|
|
231
|
-
define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute)
|
236
|
+
define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute, as: method_name)
|
232
237
|
end
|
233
238
|
|
234
239
|
# Is +new_name+ an alias?
|
@@ -441,7 +446,7 @@ module ActiveModel
|
|
441
446
|
mangled_name = name
|
442
447
|
|
443
448
|
unless NAME_COMPILABLE_REGEXP.match?(name)
|
444
|
-
mangled_name = "__temp__#{name.unpack1("h*")}"
|
449
|
+
mangled_name = :"__temp__#{name.unpack1("h*")}"
|
445
450
|
end
|
446
451
|
|
447
452
|
mangled_name
|
@@ -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 reponds 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_blank: 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
|
@@ -60,13 +62,17 @@ module ActiveModel
|
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
65
|
+
raise_on_missing = options.fetch(:raise, Translation.raise_on_missing_translations)
|
66
|
+
|
63
67
|
defaults << :"attributes.#{attribute}"
|
64
68
|
defaults << options[:default] if options[:default]
|
65
|
-
defaults << MISSING_TRANSLATION
|
69
|
+
defaults << MISSING_TRANSLATION unless raise_on_missing
|
66
70
|
|
67
|
-
translation = I18n.translate(defaults.shift, count: 1, **options, default: defaults)
|
71
|
+
translation = I18n.translate(defaults.shift, count: 1, raise: raise_on_missing, **options, default: defaults)
|
68
72
|
translation = attribute.humanize if translation == MISSING_TRANSLATION
|
69
73
|
translation
|
70
74
|
end
|
71
75
|
end
|
76
|
+
|
77
|
+
ActiveSupport.run_load_hooks(:active_model_translation, Translation)
|
72
78
|
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
|
@@ -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] = [] }
|
@@ -90,6 +69,11 @@ module ActiveModel
|
|
90
69
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
91
70
|
# <tt>on: :custom_validation_context</tt> or
|
92
71
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
72
|
+
# * <tt>:except_on</tt> - Specifies the contexts where this validation is not active.
|
73
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
74
|
+
# or an array of symbols. (e.g. <tt>except: :create</tt> or
|
75
|
+
# <tt>except_on: :custom_validation_context</tt> or
|
76
|
+
# <tt>except_on: [:create, :custom_validation_context]</tt>)
|
93
77
|
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
|
94
78
|
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
|
95
79
|
# * <tt>:if</tt> - Specifies a method, proc, or string to call to determine
|
@@ -105,7 +89,7 @@ module ActiveModel
|
|
105
89
|
validates_with BlockValidator, _merge_attributes(attr_names), &block
|
106
90
|
end
|
107
91
|
|
108
|
-
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
|
92
|
+
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend, :except_on].freeze # :nodoc:
|
109
93
|
|
110
94
|
# Adds a validation method or block to the class. This is useful when
|
111
95
|
# overriding the +validate+ instance method becomes too unwieldy and
|
@@ -156,7 +140,12 @@ module ActiveModel
|
|
156
140
|
# or an array of symbols. (e.g. <tt>on: :create</tt> or
|
157
141
|
# <tt>on: :custom_validation_context</tt> or
|
158
142
|
# <tt>on: [:create, :custom_validation_context]</tt>)
|
159
|
-
# * <tt>:
|
143
|
+
# * <tt>:except_on</tt> - Specifies the contexts where this validation is not active.
|
144
|
+
# Runs in all validation contexts by default +nil+. You can pass a symbol
|
145
|
+
# or an array of symbols. (e.g. <tt>except: :create</tt> or
|
146
|
+
# <tt>except_on: :custom_validation_context</tt> or
|
147
|
+
# <tt>except_on: [:create, :custom_validation_context]</tt>)
|
148
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
|
160
149
|
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
|
161
150
|
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
|
162
151
|
# proc or string should return or evaluate to a +true+ or +false+ value.
|
@@ -183,6 +172,15 @@ module ActiveModel
|
|
183
172
|
options = options.merge(if: [predicate_for_validation_context(options[:on]), *options[:if]])
|
184
173
|
end
|
185
174
|
|
175
|
+
if options.key?(:except_on)
|
176
|
+
options = options.dup
|
177
|
+
options[:except_on] = Array(options[:except_on])
|
178
|
+
options[:unless] = [
|
179
|
+
->(o) { (options[:except_on] & Array(o.validation_context)).any? },
|
180
|
+
*options[:unless]
|
181
|
+
]
|
182
|
+
end
|
183
|
+
|
186
184
|
set_callback(:validate, *args, options, &block)
|
187
185
|
end
|
188
186
|
|
@@ -361,15 +359,23 @@ module ActiveModel
|
|
361
359
|
# person.valid? # => true
|
362
360
|
# person.valid?(:new) # => false
|
363
361
|
def valid?(context = nil)
|
364
|
-
current_context
|
362
|
+
current_context = validation_context
|
363
|
+
context_for_validation.context = context
|
365
364
|
errors.clear
|
366
365
|
run_validations!
|
367
366
|
ensure
|
368
|
-
|
367
|
+
context_for_validation.context = current_context
|
369
368
|
end
|
370
369
|
|
371
370
|
alias_method :validate, :valid?
|
372
371
|
|
372
|
+
def freeze
|
373
|
+
errors
|
374
|
+
context_for_validation
|
375
|
+
|
376
|
+
super
|
377
|
+
end
|
378
|
+
|
373
379
|
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
|
374
380
|
# added, +false+ otherwise.
|
375
381
|
#
|
@@ -430,11 +436,38 @@ module ActiveModel
|
|
430
436
|
# end
|
431
437
|
alias :read_attribute_for_validation :send
|
432
438
|
|
439
|
+
# Returns the context when running validations.
|
440
|
+
#
|
441
|
+
# This is useful when running validations except a certain context (opposite to the +on+ option).
|
442
|
+
#
|
443
|
+
# class Person
|
444
|
+
# include ActiveModel::Validations
|
445
|
+
#
|
446
|
+
# attr_accessor :name
|
447
|
+
# validates :name, presence: true, if: -> { validation_context != :custom }
|
448
|
+
# end
|
449
|
+
#
|
450
|
+
# person = Person.new
|
451
|
+
# person.valid? #=> false
|
452
|
+
# person.valid?(:new) #=> false
|
453
|
+
# person.valid?(:custom) #=> true
|
454
|
+
def validation_context
|
455
|
+
context_for_validation.context
|
456
|
+
end
|
457
|
+
|
433
458
|
private
|
459
|
+
def validation_context=(context)
|
460
|
+
context_for_validation.context = context
|
461
|
+
end
|
462
|
+
|
463
|
+
def context_for_validation
|
464
|
+
@context_for_validation ||= ValidationContext.new
|
465
|
+
end
|
466
|
+
|
434
467
|
def init_internals
|
435
468
|
super
|
436
469
|
@errors = nil
|
437
|
-
@
|
470
|
+
@context_for_validation = nil
|
438
471
|
end
|
439
472
|
|
440
473
|
def run_validations!
|
@@ -466,6 +499,10 @@ module ActiveModel
|
|
466
499
|
super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
|
467
500
|
end
|
468
501
|
end
|
502
|
+
|
503
|
+
class ValidationContext # :nodoc:
|
504
|
+
attr_accessor :context
|
505
|
+
end
|
469
506
|
end
|
470
507
|
|
471
508
|
Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activemodel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 8.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 8.0.0.rc1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 8.0.0.rc1
|
27
27
|
description: A toolkit for building modeling frameworks like Active Record. Rich support
|
28
28
|
for attributes, callbacks, validations, serialization, internationalization, and
|
29
29
|
testing.
|
@@ -112,10 +112,10 @@ licenses:
|
|
112
112
|
- MIT
|
113
113
|
metadata:
|
114
114
|
bug_tracker_uri: https://github.com/rails/rails/issues
|
115
|
-
changelog_uri: https://github.com/rails/rails/blob/
|
116
|
-
documentation_uri: https://api.rubyonrails.org/
|
115
|
+
changelog_uri: https://github.com/rails/rails/blob/v8.0.0.rc1/activemodel/CHANGELOG.md
|
116
|
+
documentation_uri: https://api.rubyonrails.org/v8.0.0.rc1/
|
117
117
|
mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
|
118
|
-
source_code_uri: https://github.com/rails/rails/tree/
|
118
|
+
source_code_uri: https://github.com/rails/rails/tree/v8.0.0.rc1/activemodel
|
119
119
|
rubygems_mfa_required: 'true'
|
120
120
|
post_install_message:
|
121
121
|
rdoc_options: []
|
@@ -125,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
125
|
requirements:
|
126
126
|
- - ">="
|
127
127
|
- !ruby/object:Gem::Version
|
128
|
-
version: 3.
|
128
|
+
version: 3.2.0
|
129
129
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
130
|
requirements:
|
131
131
|
- - ">="
|