activemodel 5.2.7.1 → 6.1.4.6
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 +65 -111
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -4
- data/lib/active_model/attribute/user_provided_default.rb +1 -2
- data/lib/active_model/attribute.rb +21 -21
- data/lib/active_model/attribute_assignment.rb +4 -6
- data/lib/active_model/attribute_methods.rb +117 -40
- data/lib/active_model/attribute_mutation_tracker.rb +90 -33
- data/lib/active_model/attribute_set/builder.rb +81 -16
- data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
- data/lib/active_model/attribute_set.rb +20 -28
- data/lib/active_model/attributes.rb +65 -44
- data/lib/active_model/callbacks.rb +11 -9
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +51 -101
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +347 -155
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +22 -7
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +6 -0
- data/lib/active_model/secure_password.rb +54 -55
- data/lib/active_model/serialization.rb +9 -7
- data/lib/active_model/serializers/json.rb +17 -9
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/big_integer.rb +0 -1
- data/lib/active_model/type/binary.rb +1 -1
- data/lib/active_model/type/boolean.rb +0 -1
- data/lib/active_model/type/date.rb +0 -5
- data/lib/active_model/type/date_time.rb +3 -8
- data/lib/active_model/type/decimal.rb +0 -1
- data/lib/active_model/type/float.rb +2 -3
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +14 -6
- data/lib/active_model/type/helpers/numeric.rb +17 -6
- data/lib/active_model/type/helpers/time_value.rb +37 -15
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -11
- data/lib/active_model/type/integer.rb +15 -18
- data/lib/active_model/type/registry.rb +16 -16
- data/lib/active_model/type/string.rb +12 -3
- data/lib/active_model/type/time.rb +1 -6
- data/lib/active_model/type/value.rb +9 -2
- data/lib/active_model/validations/absence.rb +2 -2
- data/lib/active_model/validations/acceptance.rb +34 -27
- data/lib/active_model/validations/callbacks.rb +15 -16
- data/lib/active_model/validations/clusivity.rb +6 -3
- data/lib/active_model/validations/confirmation.rb +4 -4
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -3
- data/lib/active_model/validations/inclusion.rb +2 -2
- data/lib/active_model/validations/length.rb +3 -3
- data/lib/active_model/validations/numericality.rb +58 -44
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +7 -6
- data/lib/active_model/validations.rb +6 -9
- data/lib/active_model/validator.rb +8 -3
- data/lib/active_model.rb +2 -1
- metadata +14 -9
data/lib/active_model/errors.rb
CHANGED
@@ -4,11 +4,14 @@ require "active_support/core_ext/array/conversions"
|
|
4
4
|
require "active_support/core_ext/string/inflections"
|
5
5
|
require "active_support/core_ext/object/deep_dup"
|
6
6
|
require "active_support/core_ext/string/filters"
|
7
|
+
require "active_model/error"
|
8
|
+
require "active_model/nested_error"
|
9
|
+
require "forwardable"
|
7
10
|
|
8
11
|
module ActiveModel
|
9
12
|
# == Active \Model \Errors
|
10
13
|
#
|
11
|
-
# Provides
|
14
|
+
# Provides error related functionalities you can include in your object
|
12
15
|
# for handling error messages and interacting with Action View helpers.
|
13
16
|
#
|
14
17
|
# A minimal implementation could be:
|
@@ -59,10 +62,18 @@ module ActiveModel
|
|
59
62
|
class Errors
|
60
63
|
include Enumerable
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
extend Forwardable
|
66
|
+
def_delegators :@errors, :size, :clear, :blank?, :empty?, :uniq!, :any?
|
67
|
+
# TODO: forward all enumerable methods after `each` deprecation is removed.
|
68
|
+
def_delegators :@errors, :count
|
64
69
|
|
65
|
-
|
70
|
+
LEGACY_ATTRIBUTES = [:messages, :details].freeze
|
71
|
+
private_constant :LEGACY_ATTRIBUTES
|
72
|
+
|
73
|
+
# The actual array of +Error+ objects
|
74
|
+
# This method is aliased to <tt>objects</tt>.
|
75
|
+
attr_reader :errors
|
76
|
+
alias :objects :errors
|
66
77
|
|
67
78
|
# Pass in the instance of the object that is using the errors object.
|
68
79
|
#
|
@@ -72,18 +83,17 @@ module ActiveModel
|
|
72
83
|
# end
|
73
84
|
# end
|
74
85
|
def initialize(base)
|
75
|
-
@base
|
76
|
-
@
|
77
|
-
@details = apply_default_array({})
|
86
|
+
@base = base
|
87
|
+
@errors = []
|
78
88
|
end
|
79
89
|
|
80
90
|
def initialize_dup(other) # :nodoc:
|
81
|
-
@
|
82
|
-
@details = other.details.deep_dup
|
91
|
+
@errors = other.errors.deep_dup
|
83
92
|
super
|
84
93
|
end
|
85
94
|
|
86
95
|
# Copies the errors from <tt>other</tt>.
|
96
|
+
# For copying errors but keep <tt>@base</tt> as is.
|
87
97
|
#
|
88
98
|
# other - The ActiveModel::Errors instance.
|
89
99
|
#
|
@@ -91,11 +101,31 @@ module ActiveModel
|
|
91
101
|
#
|
92
102
|
# person.errors.copy!(other)
|
93
103
|
def copy!(other) # :nodoc:
|
94
|
-
@
|
95
|
-
@
|
104
|
+
@errors = other.errors.deep_dup
|
105
|
+
@errors.each { |error|
|
106
|
+
error.instance_variable_set(:@base, @base)
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
# Imports one error
|
111
|
+
# Imported errors are wrapped as a NestedError,
|
112
|
+
# providing access to original error object.
|
113
|
+
# If attribute or type needs to be overridden, use +override_options+.
|
114
|
+
#
|
115
|
+
# override_options - Hash
|
116
|
+
# @option override_options [Symbol] :attribute Override the attribute the error belongs to
|
117
|
+
# @option override_options [Symbol] :type Override type of the error.
|
118
|
+
def import(error, override_options = {})
|
119
|
+
[:attribute, :type].each do |key|
|
120
|
+
if override_options.key?(key)
|
121
|
+
override_options[key] = override_options[key].to_sym
|
122
|
+
end
|
123
|
+
end
|
124
|
+
@errors.append(NestedError.new(@base, error, override_options))
|
96
125
|
end
|
97
126
|
|
98
|
-
# Merges the errors from <tt>other</tt
|
127
|
+
# Merges the errors from <tt>other</tt>,
|
128
|
+
# each <tt>Error</tt> wrapped as <tt>NestedError</tt>.
|
99
129
|
#
|
100
130
|
# other - The ActiveModel::Errors instance.
|
101
131
|
#
|
@@ -103,18 +133,42 @@ module ActiveModel
|
|
103
133
|
#
|
104
134
|
# person.errors.merge!(other)
|
105
135
|
def merge!(other)
|
106
|
-
|
107
|
-
|
136
|
+
other.errors.each { |error|
|
137
|
+
import(error)
|
138
|
+
}
|
108
139
|
end
|
109
140
|
|
110
|
-
#
|
141
|
+
# Removes all errors except the given keys. Returns a hash containing the removed errors.
|
111
142
|
#
|
112
|
-
# person.errors.
|
113
|
-
# person.errors.
|
114
|
-
# person.errors.
|
115
|
-
def
|
116
|
-
|
117
|
-
|
143
|
+
# person.errors.keys # => [:name, :age, :gender, :city]
|
144
|
+
# person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
|
145
|
+
# person.errors.keys # => [:age, :gender]
|
146
|
+
def slice!(*keys)
|
147
|
+
deprecation_removal_warning(:slice!)
|
148
|
+
|
149
|
+
keys = keys.map(&:to_sym)
|
150
|
+
|
151
|
+
results = messages.dup.slice!(*keys)
|
152
|
+
|
153
|
+
@errors.keep_if do |error|
|
154
|
+
keys.include?(error.attribute)
|
155
|
+
end
|
156
|
+
|
157
|
+
results
|
158
|
+
end
|
159
|
+
|
160
|
+
# Search for errors matching +attribute+, +type+ or +options+.
|
161
|
+
#
|
162
|
+
# Only supplied params will be matched.
|
163
|
+
#
|
164
|
+
# person.errors.where(:name) # => all name errors.
|
165
|
+
# person.errors.where(:name, :too_short) # => all name errors being too short
|
166
|
+
# person.errors.where(:name, :too_short, minimum: 2) # => all name errors being too short and minimum is 2
|
167
|
+
def where(attribute, type = nil, **options)
|
168
|
+
attribute, type, options = normalize_arguments(attribute, type, **options)
|
169
|
+
@errors.select { |error|
|
170
|
+
error.match?(attribute, type, **options)
|
171
|
+
}
|
118
172
|
end
|
119
173
|
|
120
174
|
# Returns +true+ if the error messages include an error for the given key
|
@@ -124,8 +178,9 @@ module ActiveModel
|
|
124
178
|
# person.errors.include?(:name) # => true
|
125
179
|
# person.errors.include?(:age) # => false
|
126
180
|
def include?(attribute)
|
127
|
-
|
128
|
-
|
181
|
+
@errors.any? { |error|
|
182
|
+
error.match?(attribute.to_sym)
|
183
|
+
}
|
129
184
|
end
|
130
185
|
alias :has_key? :include?
|
131
186
|
alias :key? :include?
|
@@ -135,10 +190,13 @@ module ActiveModel
|
|
135
190
|
# person.errors[:name] # => ["cannot be nil"]
|
136
191
|
# person.errors.delete(:name) # => ["cannot be nil"]
|
137
192
|
# person.errors[:name] # => []
|
138
|
-
def delete(
|
139
|
-
attribute =
|
140
|
-
|
141
|
-
|
193
|
+
def delete(attribute, type = nil, **options)
|
194
|
+
attribute, type, options = normalize_arguments(attribute, type, **options)
|
195
|
+
matches = where(attribute, type, **options)
|
196
|
+
matches.each do |error|
|
197
|
+
@errors.delete(error)
|
198
|
+
end
|
199
|
+
matches.map(&:message).presence
|
142
200
|
end
|
143
201
|
|
144
202
|
# When passed a symbol or a name of a method, returns an array of errors
|
@@ -147,48 +205,65 @@ module ActiveModel
|
|
147
205
|
# person.errors[:name] # => ["cannot be nil"]
|
148
206
|
# person.errors['name'] # => ["cannot be nil"]
|
149
207
|
def [](attribute)
|
150
|
-
|
208
|
+
DeprecationHandlingMessageArray.new(messages_for(attribute), self, attribute)
|
151
209
|
end
|
152
210
|
|
153
|
-
# Iterates through each error
|
211
|
+
# Iterates through each error object.
|
212
|
+
#
|
213
|
+
# person.errors.add(:name, :too_short, count: 2)
|
214
|
+
# person.errors.each do |error|
|
215
|
+
# # Will yield <#ActiveModel::Error attribute=name, type=too_short,
|
216
|
+
# options={:count=>3}>
|
217
|
+
# end
|
218
|
+
#
|
219
|
+
# To be backward compatible with past deprecated hash-like behavior,
|
220
|
+
# when block accepts two parameters instead of one, it
|
221
|
+
# iterates through each error key, value pair in the error messages hash.
|
154
222
|
# Yields the attribute and the error for that attribute. If the attribute
|
155
223
|
# has more than one error message, yields once for each error message.
|
156
224
|
#
|
157
225
|
# person.errors.add(:name, :blank, message: "can't be blank")
|
158
|
-
# person.errors.each do |attribute,
|
226
|
+
# person.errors.each do |attribute, message|
|
159
227
|
# # Will yield :name and "can't be blank"
|
160
228
|
# end
|
161
229
|
#
|
162
230
|
# person.errors.add(:name, :not_specified, message: "must be specified")
|
163
|
-
# person.errors.each do |attribute,
|
231
|
+
# person.errors.each do |attribute, message|
|
164
232
|
# # Will yield :name and "can't be blank"
|
165
233
|
# # then yield :name and "must be specified"
|
166
234
|
# end
|
167
|
-
def each
|
168
|
-
|
169
|
-
|
235
|
+
def each(&block)
|
236
|
+
if block.arity <= 1
|
237
|
+
@errors.each(&block)
|
238
|
+
else
|
239
|
+
ActiveSupport::Deprecation.warn(<<~MSG)
|
240
|
+
Enumerating ActiveModel::Errors as a hash has been deprecated.
|
241
|
+
In Rails 6.1, `errors` is an array of Error objects,
|
242
|
+
therefore it should be accessed by a block with a single block
|
243
|
+
parameter like this:
|
244
|
+
|
245
|
+
person.errors.each do |error|
|
246
|
+
attribute = error.attribute
|
247
|
+
message = error.message
|
248
|
+
end
|
249
|
+
|
250
|
+
You are passing a block expecting two parameters,
|
251
|
+
so the old hash behavior is simulated. As this is deprecated,
|
252
|
+
this will result in an ArgumentError in Rails 6.2.
|
253
|
+
MSG
|
254
|
+
@errors.
|
255
|
+
sort { |a, b| a.attribute <=> b.attribute }.
|
256
|
+
each { |error| yield error.attribute, error.message }
|
170
257
|
end
|
171
258
|
end
|
172
259
|
|
173
|
-
# Returns the number of error messages.
|
174
|
-
#
|
175
|
-
# person.errors.add(:name, :blank, message: "can't be blank")
|
176
|
-
# person.errors.size # => 1
|
177
|
-
# person.errors.add(:name, :not_specified, message: "must be specified")
|
178
|
-
# person.errors.size # => 2
|
179
|
-
def size
|
180
|
-
values.flatten.size
|
181
|
-
end
|
182
|
-
alias :count :size
|
183
|
-
|
184
260
|
# Returns all message values.
|
185
261
|
#
|
186
262
|
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
187
263
|
# person.errors.values # => [["cannot be nil", "must be specified"]]
|
188
264
|
def values
|
189
|
-
|
190
|
-
|
191
|
-
end.values
|
265
|
+
deprecation_removal_warning(:values, "errors.map { |error| error.message }")
|
266
|
+
@errors.map(&:message).freeze
|
192
267
|
end
|
193
268
|
|
194
269
|
# Returns all message keys.
|
@@ -196,20 +271,19 @@ module ActiveModel
|
|
196
271
|
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
197
272
|
# person.errors.keys # => [:name]
|
198
273
|
def keys
|
199
|
-
|
200
|
-
|
201
|
-
|
274
|
+
deprecation_removal_warning(:keys, "errors.attribute_names")
|
275
|
+
keys = @errors.map(&:attribute)
|
276
|
+
keys.uniq!
|
277
|
+
keys.freeze
|
202
278
|
end
|
203
279
|
|
204
|
-
# Returns
|
205
|
-
# If the error message is a string it can be empty.
|
280
|
+
# Returns all error attribute names
|
206
281
|
#
|
207
|
-
# person.errors.
|
208
|
-
# person.errors.
|
209
|
-
def
|
210
|
-
|
282
|
+
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
283
|
+
# person.errors.attribute_names # => [:name]
|
284
|
+
def attribute_names
|
285
|
+
@errors.map(&:attribute).uniq.freeze
|
211
286
|
end
|
212
|
-
alias :blank? :empty?
|
213
287
|
|
214
288
|
# Returns an xml formatted representation of the Errors hash.
|
215
289
|
#
|
@@ -223,6 +297,7 @@ module ActiveModel
|
|
223
297
|
# # <error>name must be specified</error>
|
224
298
|
# # </errors>
|
225
299
|
def to_xml(options = {})
|
300
|
+
deprecation_removal_warning(:to_xml)
|
226
301
|
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
|
227
302
|
end
|
228
303
|
|
@@ -242,34 +317,68 @@ module ActiveModel
|
|
242
317
|
# person.errors.to_hash # => {:name=>["cannot be nil"]}
|
243
318
|
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
|
244
319
|
def to_hash(full_messages = false)
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
320
|
+
message_method = full_messages ? :full_message : :message
|
321
|
+
group_by_attribute.transform_values do |errors|
|
322
|
+
errors.map(&message_method)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def to_h
|
327
|
+
ActiveSupport::Deprecation.warn(<<~EOM)
|
328
|
+
ActiveModel::Errors#to_h is deprecated and will be removed in Rails 6.2.
|
329
|
+
Please use `ActiveModel::Errors.to_hash` instead. The values in the hash
|
330
|
+
returned by `ActiveModel::Errors.to_hash` is an array of error messages.
|
331
|
+
EOM
|
332
|
+
|
333
|
+
to_hash.transform_values { |values| values.last }
|
334
|
+
end
|
335
|
+
|
336
|
+
# Returns a Hash of attributes with an array of their error messages.
|
337
|
+
#
|
338
|
+
# Updating this hash would still update errors state for backward
|
339
|
+
# compatibility, but this behavior is deprecated.
|
340
|
+
def messages
|
341
|
+
DeprecationHandlingMessageHash.new(self)
|
342
|
+
end
|
343
|
+
|
344
|
+
# Returns a Hash of attributes with an array of their error details.
|
345
|
+
#
|
346
|
+
# Updating this hash would still update errors state for backward
|
347
|
+
# compatibility, but this behavior is deprecated.
|
348
|
+
def details
|
349
|
+
hash = group_by_attribute.transform_values do |errors|
|
350
|
+
errors.map(&:details)
|
251
351
|
end
|
352
|
+
DeprecationHandlingDetailsHash.new(hash)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Returns a Hash of attributes with an array of their Error objects.
|
356
|
+
#
|
357
|
+
# person.errors.group_by_attribute
|
358
|
+
# # => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]}
|
359
|
+
def group_by_attribute
|
360
|
+
@errors.group_by(&:attribute)
|
252
361
|
end
|
253
362
|
|
254
|
-
# Adds
|
363
|
+
# Adds a new error of +type+ on +attribute+.
|
255
364
|
# More than one error can be added to the same +attribute+.
|
256
|
-
# If no +
|
365
|
+
# If no +type+ is supplied, <tt>:invalid</tt> is assumed.
|
257
366
|
#
|
258
367
|
# person.errors.add(:name)
|
259
|
-
# #
|
368
|
+
# # Adds <#ActiveModel::Error attribute=name, type=invalid>
|
260
369
|
# person.errors.add(:name, :not_implemented, message: "must be implemented")
|
261
|
-
# #
|
370
|
+
# # Adds <#ActiveModel::Error attribute=name, type=not_implemented,
|
371
|
+
# options={:message=>"must be implemented"}>
|
262
372
|
#
|
263
373
|
# person.errors.messages
|
264
374
|
# # => {:name=>["is invalid", "must be implemented"]}
|
265
375
|
#
|
266
|
-
#
|
267
|
-
# # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
|
376
|
+
# If +type+ is a string, it will be used as error message.
|
268
377
|
#
|
269
|
-
# If +
|
378
|
+
# If +type+ is a symbol, it will be translated using the appropriate
|
270
379
|
# scope (see +generate_message+).
|
271
380
|
#
|
272
|
-
# If +
|
381
|
+
# If +type+ is a proc, it will be called, allowing for things like
|
273
382
|
# <tt>Time.now</tt> to be used within an error.
|
274
383
|
#
|
275
384
|
# If the <tt>:strict</tt> option is set to +true+, it will raise
|
@@ -292,42 +401,66 @@ module ActiveModel
|
|
292
401
|
# # => {:base=>["either name or email must be present"]}
|
293
402
|
# person.errors.details
|
294
403
|
# # => {:base=>[{error: :name_or_email_blank}]}
|
295
|
-
def add(attribute,
|
296
|
-
|
297
|
-
|
298
|
-
|
404
|
+
def add(attribute, type = :invalid, **options)
|
405
|
+
attribute, type, options = normalize_arguments(attribute, type, **options)
|
406
|
+
error = Error.new(@base, attribute, type, **options)
|
407
|
+
|
299
408
|
if exception = options[:strict]
|
300
409
|
exception = ActiveModel::StrictValidationFailed if exception == true
|
301
|
-
raise exception, full_message
|
410
|
+
raise exception, error.full_message
|
302
411
|
end
|
303
412
|
|
304
|
-
|
305
|
-
|
413
|
+
@errors.append(error)
|
414
|
+
|
415
|
+
error
|
306
416
|
end
|
307
417
|
|
308
|
-
# Returns +true+ if an error
|
309
|
-
#
|
418
|
+
# Returns +true+ if an error matches provided +attribute+ and +type+,
|
419
|
+
# or +false+ otherwise. +type+ is treated the same as for +add+.
|
310
420
|
#
|
311
421
|
# person.errors.add :name, :blank
|
312
422
|
# person.errors.added? :name, :blank # => true
|
313
423
|
# person.errors.added? :name, "can't be blank" # => true
|
314
424
|
#
|
315
|
-
# If the error
|
316
|
-
# the correct
|
317
|
-
#
|
318
|
-
#
|
319
|
-
#
|
320
|
-
#
|
321
|
-
#
|
322
|
-
#
|
323
|
-
#
|
324
|
-
def added?(attribute,
|
325
|
-
|
425
|
+
# If the error requires options, then it returns +true+ with
|
426
|
+
# the correct options, or +false+ with incorrect or missing options.
|
427
|
+
#
|
428
|
+
# person.errors.add :name, :too_long, { count: 25 }
|
429
|
+
# person.errors.added? :name, :too_long, count: 25 # => true
|
430
|
+
# person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
|
431
|
+
# person.errors.added? :name, :too_long, count: 24 # => false
|
432
|
+
# person.errors.added? :name, :too_long # => false
|
433
|
+
# person.errors.added? :name, "is too long" # => false
|
434
|
+
def added?(attribute, type = :invalid, options = {})
|
435
|
+
attribute, type, options = normalize_arguments(attribute, type, **options)
|
436
|
+
|
437
|
+
if type.is_a? Symbol
|
438
|
+
@errors.any? { |error|
|
439
|
+
error.strict_match?(attribute, type, **options)
|
440
|
+
}
|
441
|
+
else
|
442
|
+
messages_for(attribute).include?(type)
|
443
|
+
end
|
444
|
+
end
|
326
445
|
|
327
|
-
|
328
|
-
|
446
|
+
# Returns +true+ if an error on the attribute with the given type is
|
447
|
+
# present, or +false+ otherwise. +type+ is treated the same as for +add+.
|
448
|
+
#
|
449
|
+
# person.errors.add :age
|
450
|
+
# person.errors.add :name, :too_long, { count: 25 }
|
451
|
+
# person.errors.of_kind? :age # => true
|
452
|
+
# person.errors.of_kind? :name # => false
|
453
|
+
# person.errors.of_kind? :name, :too_long # => true
|
454
|
+
# person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
|
455
|
+
# person.errors.of_kind? :name, :not_too_long # => false
|
456
|
+
# person.errors.of_kind? :name, "is too long" # => false
|
457
|
+
def of_kind?(attribute, type = :invalid)
|
458
|
+
attribute, type = normalize_arguments(attribute, type)
|
459
|
+
|
460
|
+
if type.is_a? Symbol
|
461
|
+
!where(attribute, type).empty?
|
329
462
|
else
|
330
|
-
|
463
|
+
messages_for(attribute).include?(type)
|
331
464
|
end
|
332
465
|
end
|
333
466
|
|
@@ -342,7 +475,7 @@ module ActiveModel
|
|
342
475
|
# person.errors.full_messages
|
343
476
|
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
|
344
477
|
def full_messages
|
345
|
-
map
|
478
|
+
@errors.map(&:full_message)
|
346
479
|
end
|
347
480
|
alias :to_a :full_messages
|
348
481
|
|
@@ -357,21 +490,28 @@ module ActiveModel
|
|
357
490
|
# person.errors.full_messages_for(:name)
|
358
491
|
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
|
359
492
|
def full_messages_for(attribute)
|
360
|
-
attribute
|
361
|
-
|
493
|
+
where(attribute).map(&:full_message).freeze
|
494
|
+
end
|
495
|
+
|
496
|
+
# Returns all the error messages for a given attribute in an array.
|
497
|
+
#
|
498
|
+
# class Person
|
499
|
+
# validates_presence_of :name, :email
|
500
|
+
# validates_length_of :name, in: 5..30
|
501
|
+
# end
|
502
|
+
#
|
503
|
+
# person = Person.create()
|
504
|
+
# person.errors.messages_for(:name)
|
505
|
+
# # => ["is too short (minimum is 5 characters)", "can't be blank"]
|
506
|
+
def messages_for(attribute)
|
507
|
+
where(attribute).map(&:message)
|
362
508
|
end
|
363
509
|
|
364
510
|
# Returns a full message for a given attribute.
|
365
511
|
#
|
366
512
|
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
|
367
513
|
def full_message(attribute, message)
|
368
|
-
|
369
|
-
attr_name = attribute.to_s.tr(".", "_").humanize
|
370
|
-
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
|
371
|
-
I18n.t(:"errors.format",
|
372
|
-
default: "%{attribute} %{message}",
|
373
|
-
attribute: attr_name,
|
374
|
-
message: message)
|
514
|
+
Error.full_message(attribute, message, @base)
|
375
515
|
end
|
376
516
|
|
377
517
|
# Translates an error message in its default scope
|
@@ -399,77 +539,129 @@ module ActiveModel
|
|
399
539
|
# * <tt>errors.attributes.title.blank</tt>
|
400
540
|
# * <tt>errors.messages.blank</tt>
|
401
541
|
def generate_message(attribute, type = :invalid, options = {})
|
402
|
-
|
542
|
+
Error.generate_message(attribute, type, @base, options)
|
543
|
+
end
|
403
544
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
545
|
+
def marshal_load(array) # :nodoc:
|
546
|
+
# Rails 5
|
547
|
+
@errors = []
|
548
|
+
@base = array[0]
|
549
|
+
add_from_legacy_details_hash(array[2])
|
550
|
+
end
|
551
|
+
|
552
|
+
def init_with(coder) # :nodoc:
|
553
|
+
data = coder.map
|
554
|
+
|
555
|
+
data.each { |k, v|
|
556
|
+
next if LEGACY_ATTRIBUTES.include?(k.to_sym)
|
557
|
+
instance_variable_set(:"@#{k}", v)
|
558
|
+
}
|
559
|
+
|
560
|
+
@errors ||= []
|
561
|
+
|
562
|
+
# Legacy support Rails 5.x details hash
|
563
|
+
add_from_legacy_details_hash(data["details"]) if data.key?("details")
|
564
|
+
end
|
565
|
+
|
566
|
+
private
|
567
|
+
def normalize_arguments(attribute, type, **options)
|
568
|
+
# Evaluate proc first
|
569
|
+
if type.respond_to?(:call)
|
570
|
+
type = type.call(@base, options)
|
409
571
|
end
|
410
|
-
|
411
|
-
|
412
|
-
defaults = []
|
572
|
+
|
573
|
+
[attribute.to_sym, type, options]
|
413
574
|
end
|
414
575
|
|
415
|
-
|
416
|
-
|
576
|
+
def add_from_legacy_details_hash(details)
|
577
|
+
details.each { |attribute, errors|
|
578
|
+
errors.each { |error|
|
579
|
+
type = error.delete(:error)
|
580
|
+
add(attribute, type, **error)
|
581
|
+
}
|
582
|
+
}
|
583
|
+
end
|
417
584
|
|
418
|
-
|
419
|
-
|
420
|
-
|
585
|
+
def deprecation_removal_warning(method_name, alternative_message = nil)
|
586
|
+
message = +"ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails 6.2."
|
587
|
+
if alternative_message
|
588
|
+
message << "\n\nTo achieve the same use:\n\n "
|
589
|
+
message << alternative_message
|
590
|
+
end
|
591
|
+
ActiveSupport::Deprecation.warn(message)
|
592
|
+
end
|
421
593
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
value: value,
|
427
|
-
object: @base
|
428
|
-
}.merge!(options)
|
594
|
+
def deprecation_rename_warning(old_method_name, new_method_name)
|
595
|
+
ActiveSupport::Deprecation.warn("ActiveModel::Errors##{old_method_name} is deprecated. Please call ##{new_method_name} instead.")
|
596
|
+
end
|
597
|
+
end
|
429
598
|
|
430
|
-
|
599
|
+
class DeprecationHandlingMessageHash < SimpleDelegator
|
600
|
+
def initialize(errors)
|
601
|
+
@errors = errors
|
602
|
+
super(prepare_content)
|
431
603
|
end
|
432
604
|
|
433
|
-
def
|
434
|
-
[
|
435
|
-
end
|
605
|
+
def []=(attribute, value)
|
606
|
+
ActiveSupport::Deprecation.warn("Calling `[]=` to an ActiveModel::Errors is deprecated. Please call `ActiveModel::Errors#add` instead.")
|
436
607
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
608
|
+
@errors.delete(attribute)
|
609
|
+
Array(value).each do |message|
|
610
|
+
@errors.add(attribute, message)
|
611
|
+
end
|
612
|
+
|
613
|
+
__setobj__ prepare_content
|
441
614
|
end
|
442
615
|
|
443
|
-
def
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
apply_default_array(@details)
|
616
|
+
def delete(attribute)
|
617
|
+
ActiveSupport::Deprecation.warn("Calling `delete` to an ActiveModel::Errors messages hash is deprecated. Please call `ActiveModel::Errors#delete` instead.")
|
618
|
+
|
619
|
+
@errors.delete(attribute)
|
448
620
|
end
|
449
621
|
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
622
|
+
private
|
623
|
+
def prepare_content
|
624
|
+
content = @errors.to_hash
|
625
|
+
content.each do |attribute, value|
|
626
|
+
content[attribute] = DeprecationHandlingMessageArray.new(value, @errors, attribute)
|
627
|
+
end
|
628
|
+
content.default_proc = proc do |hash, attribute|
|
629
|
+
hash = hash.dup
|
630
|
+
hash[attribute] = DeprecationHandlingMessageArray.new([], @errors, attribute)
|
631
|
+
__setobj__ hash.freeze
|
632
|
+
hash[attribute]
|
633
|
+
end
|
634
|
+
content.freeze
|
457
635
|
end
|
636
|
+
end
|
637
|
+
|
638
|
+
class DeprecationHandlingMessageArray < SimpleDelegator
|
639
|
+
def initialize(content, errors, attribute)
|
640
|
+
@errors = errors
|
641
|
+
@attribute = attribute
|
642
|
+
super(content.freeze)
|
458
643
|
end
|
459
644
|
|
460
|
-
def
|
461
|
-
|
645
|
+
def <<(message)
|
646
|
+
ActiveSupport::Deprecation.warn("Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated. Please call `ActiveModel::Errors#add` instead.")
|
647
|
+
|
648
|
+
@errors.add(@attribute, message)
|
649
|
+
__setobj__ @errors.messages_for(@attribute)
|
650
|
+
self
|
462
651
|
end
|
463
652
|
|
464
|
-
def
|
465
|
-
|
466
|
-
|
467
|
-
|
653
|
+
def clear
|
654
|
+
ActiveSupport::Deprecation.warn("Calling `clear` to an ActiveModel::Errors message array in order to delete all errors is deprecated. Please call `ActiveModel::Errors#delete` instead.")
|
655
|
+
|
656
|
+
@errors.delete(@attribute)
|
468
657
|
end
|
658
|
+
end
|
469
659
|
|
470
|
-
|
471
|
-
|
472
|
-
|
660
|
+
class DeprecationHandlingDetailsHash < SimpleDelegator
|
661
|
+
def initialize(details)
|
662
|
+
details.default = []
|
663
|
+
details.freeze
|
664
|
+
super(details)
|
473
665
|
end
|
474
666
|
end
|
475
667
|
|