activemodel 6.0.3.2 → 6.1.0
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 +48 -182
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/lib/active_model.rb +2 -1
- data/lib/active_model/attribute.rb +15 -14
- data/lib/active_model/attribute_assignment.rb +3 -4
- data/lib/active_model/attribute_methods.rb +74 -38
- data/lib/active_model/attribute_mutation_tracker.rb +8 -5
- data/lib/active_model/attribute_set.rb +18 -16
- data/lib/active_model/attribute_set/builder.rb +80 -13
- data/lib/active_model/attributes.rb +20 -24
- data/lib/active_model/dirty.rb +12 -4
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +316 -208
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/naming.rb +2 -2
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +1 -1
- data/lib/active_model/secure_password.rb +14 -14
- data/lib/active_model/serialization.rb +9 -6
- data/lib/active_model/serializers/json.rb +7 -0
- data/lib/active_model/type/date_time.rb +2 -2
- data/lib/active_model/type/float.rb +2 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
- data/lib/active_model/type/helpers/numeric.rb +8 -3
- data/lib/active_model/type/helpers/time_value.rb +27 -17
- data/lib/active_model/type/helpers/timezone.rb +1 -1
- data/lib/active_model/type/immutable_string.rb +14 -10
- data/lib/active_model/type/integer.rb +11 -2
- data/lib/active_model/type/registry.rb +11 -4
- data/lib/active_model/type/string.rb +12 -2
- data/lib/active_model/type/value.rb +9 -1
- data/lib/active_model/validations.rb +6 -6
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/clusivity.rb +5 -1
- data/lib/active_model/validations/confirmation.rb +2 -2
- data/lib/active_model/validations/exclusion.rb +1 -1
- data/lib/active_model/validations/format.rb +2 -2
- data/lib/active_model/validations/inclusion.rb +1 -1
- data/lib/active_model/validations/length.rb +2 -2
- data/lib/active_model/validations/numericality.rb +48 -41
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/validates.rb +6 -4
- data/lib/active_model/validator.rb +7 -1
- metadata +13 -11
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,15 +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
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
self.i18n_customize_full_message = false
|
70
|
+
LEGACY_ATTRIBUTES = [:messages, :details].freeze
|
71
|
+
private_constant :LEGACY_ATTRIBUTES
|
69
72
|
|
70
|
-
|
73
|
+
# The actual array of +Error+ objects
|
74
|
+
# This method is aliased to <tt>objects</tt>.
|
75
|
+
attr_reader :errors
|
76
|
+
alias :objects :errors
|
71
77
|
|
72
78
|
# Pass in the instance of the object that is using the errors object.
|
73
79
|
#
|
@@ -77,18 +83,17 @@ module ActiveModel
|
|
77
83
|
# end
|
78
84
|
# end
|
79
85
|
def initialize(base)
|
80
|
-
@base
|
81
|
-
@
|
82
|
-
@details = apply_default_array({})
|
86
|
+
@base = base
|
87
|
+
@errors = []
|
83
88
|
end
|
84
89
|
|
85
90
|
def initialize_dup(other) # :nodoc:
|
86
|
-
@
|
87
|
-
@details = other.details.deep_dup
|
91
|
+
@errors = other.errors.deep_dup
|
88
92
|
super
|
89
93
|
end
|
90
94
|
|
91
95
|
# Copies the errors from <tt>other</tt>.
|
96
|
+
# For copying errors but keep <tt>@base</tt> as is.
|
92
97
|
#
|
93
98
|
# other - The ActiveModel::Errors instance.
|
94
99
|
#
|
@@ -96,11 +101,31 @@ module ActiveModel
|
|
96
101
|
#
|
97
102
|
# person.errors.copy!(other)
|
98
103
|
def copy!(other) # :nodoc:
|
99
|
-
@
|
100
|
-
@
|
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))
|
101
125
|
end
|
102
126
|
|
103
|
-
# 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>.
|
104
129
|
#
|
105
130
|
# other - The ActiveModel::Errors instance.
|
106
131
|
#
|
@@ -108,8 +133,9 @@ module ActiveModel
|
|
108
133
|
#
|
109
134
|
# person.errors.merge!(other)
|
110
135
|
def merge!(other)
|
111
|
-
|
112
|
-
|
136
|
+
other.errors.each { |error|
|
137
|
+
import(error)
|
138
|
+
}
|
113
139
|
end
|
114
140
|
|
115
141
|
# Removes all errors except the given keys. Returns a hash containing the removed errors.
|
@@ -118,19 +144,31 @@ module ActiveModel
|
|
118
144
|
# person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
|
119
145
|
# person.errors.keys # => [:age, :gender]
|
120
146
|
def slice!(*keys)
|
147
|
+
deprecation_removal_warning(:slice!)
|
148
|
+
|
121
149
|
keys = keys.map(&:to_sym)
|
122
|
-
|
123
|
-
|
150
|
+
|
151
|
+
results = messages.dup.slice!(*keys)
|
152
|
+
|
153
|
+
@errors.keep_if do |error|
|
154
|
+
keys.include?(error.attribute)
|
155
|
+
end
|
156
|
+
|
157
|
+
results
|
124
158
|
end
|
125
159
|
|
126
|
-
#
|
160
|
+
# Search for errors matching +attribute+, +type+ or +options+.
|
127
161
|
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# person.errors.
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
+
}
|
134
172
|
end
|
135
173
|
|
136
174
|
# Returns +true+ if the error messages include an error for the given key
|
@@ -140,8 +178,9 @@ module ActiveModel
|
|
140
178
|
# person.errors.include?(:name) # => true
|
141
179
|
# person.errors.include?(:age) # => false
|
142
180
|
def include?(attribute)
|
143
|
-
|
144
|
-
|
181
|
+
@errors.any? { |error|
|
182
|
+
error.match?(attribute.to_sym)
|
183
|
+
}
|
145
184
|
end
|
146
185
|
alias :has_key? :include?
|
147
186
|
alias :key? :include?
|
@@ -151,10 +190,13 @@ module ActiveModel
|
|
151
190
|
# person.errors[:name] # => ["cannot be nil"]
|
152
191
|
# person.errors.delete(:name) # => ["cannot be nil"]
|
153
192
|
# person.errors[:name] # => []
|
154
|
-
def delete(
|
155
|
-
attribute =
|
156
|
-
|
157
|
-
|
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
|
158
200
|
end
|
159
201
|
|
160
202
|
# When passed a symbol or a name of a method, returns an array of errors
|
@@ -163,48 +205,65 @@ module ActiveModel
|
|
163
205
|
# person.errors[:name] # => ["cannot be nil"]
|
164
206
|
# person.errors['name'] # => ["cannot be nil"]
|
165
207
|
def [](attribute)
|
166
|
-
|
208
|
+
DeprecationHandlingMessageArray.new(messages_for(attribute), self, attribute)
|
167
209
|
end
|
168
210
|
|
169
|
-
# 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.
|
170
222
|
# Yields the attribute and the error for that attribute. If the attribute
|
171
223
|
# has more than one error message, yields once for each error message.
|
172
224
|
#
|
173
225
|
# person.errors.add(:name, :blank, message: "can't be blank")
|
174
|
-
# person.errors.each do |attribute,
|
226
|
+
# person.errors.each do |attribute, message|
|
175
227
|
# # Will yield :name and "can't be blank"
|
176
228
|
# end
|
177
229
|
#
|
178
230
|
# person.errors.add(:name, :not_specified, message: "must be specified")
|
179
|
-
# person.errors.each do |attribute,
|
231
|
+
# person.errors.each do |attribute, message|
|
180
232
|
# # Will yield :name and "can't be blank"
|
181
233
|
# # then yield :name and "must be specified"
|
182
234
|
# end
|
183
|
-
def each
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
188
249
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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 }
|
257
|
+
end
|
197
258
|
end
|
198
|
-
alias :count :size
|
199
259
|
|
200
260
|
# Returns all message values.
|
201
261
|
#
|
202
262
|
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
203
263
|
# person.errors.values # => [["cannot be nil", "must be specified"]]
|
204
264
|
def values
|
205
|
-
|
206
|
-
|
207
|
-
end.values
|
265
|
+
deprecation_removal_warning(:values, "errors.map { |error| error.message }")
|
266
|
+
@errors.map(&:message).freeze
|
208
267
|
end
|
209
268
|
|
210
269
|
# Returns all message keys.
|
@@ -212,20 +271,19 @@ module ActiveModel
|
|
212
271
|
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
213
272
|
# person.errors.keys # => [:name]
|
214
273
|
def keys
|
215
|
-
|
216
|
-
|
217
|
-
|
274
|
+
deprecation_removal_warning(:keys, "errors.attribute_names")
|
275
|
+
keys = @errors.map(&:attribute)
|
276
|
+
keys.uniq!
|
277
|
+
keys.freeze
|
218
278
|
end
|
219
279
|
|
220
|
-
# Returns
|
221
|
-
# If the error message is a string it can be empty.
|
280
|
+
# Returns all error attribute names
|
222
281
|
#
|
223
|
-
# person.errors.
|
224
|
-
# person.errors.
|
225
|
-
def
|
226
|
-
|
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
|
227
286
|
end
|
228
|
-
alias :blank? :empty?
|
229
287
|
|
230
288
|
# Returns an xml formatted representation of the Errors hash.
|
231
289
|
#
|
@@ -239,6 +297,7 @@ module ActiveModel
|
|
239
297
|
# # <error>name must be specified</error>
|
240
298
|
# # </errors>
|
241
299
|
def to_xml(options = {})
|
300
|
+
deprecation_removal_warning(:to_xml)
|
242
301
|
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
|
243
302
|
end
|
244
303
|
|
@@ -258,34 +317,68 @@ module ActiveModel
|
|
258
317
|
# person.errors.to_hash # => {:name=>["cannot be nil"]}
|
259
318
|
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
|
260
319
|
def to_hash(full_messages = false)
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
end
|
265
|
-
else
|
266
|
-
without_default_proc(messages)
|
320
|
+
message_method = full_messages ? :full_message : :message
|
321
|
+
group_by_attribute.transform_values do |errors|
|
322
|
+
errors.map(&message_method)
|
267
323
|
end
|
268
324
|
end
|
269
325
|
|
270
|
-
|
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)
|
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)
|
361
|
+
end
|
362
|
+
|
363
|
+
# Adds a new error of +type+ on +attribute+.
|
271
364
|
# More than one error can be added to the same +attribute+.
|
272
|
-
# If no +
|
365
|
+
# If no +type+ is supplied, <tt>:invalid</tt> is assumed.
|
273
366
|
#
|
274
367
|
# person.errors.add(:name)
|
275
|
-
# #
|
368
|
+
# # Adds <#ActiveModel::Error attribute=name, type=invalid>
|
276
369
|
# person.errors.add(:name, :not_implemented, message: "must be implemented")
|
277
|
-
# #
|
370
|
+
# # Adds <#ActiveModel::Error attribute=name, type=not_implemented,
|
371
|
+
# options={:message=>"must be implemented"}>
|
278
372
|
#
|
279
373
|
# person.errors.messages
|
280
374
|
# # => {:name=>["is invalid", "must be implemented"]}
|
281
375
|
#
|
282
|
-
#
|
283
|
-
# # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
|
376
|
+
# If +type+ is a string, it will be used as error message.
|
284
377
|
#
|
285
|
-
# If +
|
378
|
+
# If +type+ is a symbol, it will be translated using the appropriate
|
286
379
|
# scope (see +generate_message+).
|
287
380
|
#
|
288
|
-
# If +
|
381
|
+
# If +type+ is a proc, it will be called, allowing for things like
|
289
382
|
# <tt>Time.now</tt> to be used within an error.
|
290
383
|
#
|
291
384
|
# If the <tt>:strict</tt> option is set to +true+, it will raise
|
@@ -308,27 +401,28 @@ module ActiveModel
|
|
308
401
|
# # => {:base=>["either name or email must be present"]}
|
309
402
|
# person.errors.details
|
310
403
|
# # => {:base=>[{error: :name_or_email_blank}]}
|
311
|
-
def add(attribute,
|
312
|
-
|
313
|
-
|
314
|
-
|
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
|
+
|
315
408
|
if exception = options[:strict]
|
316
409
|
exception = ActiveModel::StrictValidationFailed if exception == true
|
317
|
-
raise exception, full_message
|
410
|
+
raise exception, error.full_message
|
318
411
|
end
|
319
412
|
|
320
|
-
|
321
|
-
|
413
|
+
@errors.append(error)
|
414
|
+
|
415
|
+
error
|
322
416
|
end
|
323
417
|
|
324
|
-
# Returns +true+ if an error
|
325
|
-
#
|
418
|
+
# Returns +true+ if an error matches provided +attribute+ and +type+,
|
419
|
+
# or +false+ otherwise. +type+ is treated the same as for +add+.
|
326
420
|
#
|
327
421
|
# person.errors.add :name, :blank
|
328
422
|
# person.errors.added? :name, :blank # => true
|
329
423
|
# person.errors.added? :name, "can't be blank" # => true
|
330
424
|
#
|
331
|
-
# If the error
|
425
|
+
# If the error requires options, then it returns +true+ with
|
332
426
|
# the correct options, or +false+ with incorrect or missing options.
|
333
427
|
#
|
334
428
|
# person.errors.add :name, :too_long, { count: 25 }
|
@@ -337,18 +431,20 @@ module ActiveModel
|
|
337
431
|
# person.errors.added? :name, :too_long, count: 24 # => false
|
338
432
|
# person.errors.added? :name, :too_long # => false
|
339
433
|
# person.errors.added? :name, "is too long" # => false
|
340
|
-
def added?(attribute,
|
341
|
-
|
434
|
+
def added?(attribute, type = :invalid, options = {})
|
435
|
+
attribute, type, options = normalize_arguments(attribute, type, **options)
|
342
436
|
|
343
|
-
if
|
344
|
-
|
437
|
+
if type.is_a? Symbol
|
438
|
+
@errors.any? { |error|
|
439
|
+
error.strict_match?(attribute, type, **options)
|
440
|
+
}
|
345
441
|
else
|
346
|
-
|
442
|
+
messages_for(attribute).include?(type)
|
347
443
|
end
|
348
444
|
end
|
349
445
|
|
350
|
-
# Returns +true+ if an error on the attribute with the given
|
351
|
-
# present, or +false+ otherwise. +
|
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+.
|
352
448
|
#
|
353
449
|
# person.errors.add :age
|
354
450
|
# person.errors.add :name, :too_long, { count: 25 }
|
@@ -358,13 +454,13 @@ module ActiveModel
|
|
358
454
|
# person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
|
359
455
|
# person.errors.of_kind? :name, :not_too_long # => false
|
360
456
|
# person.errors.of_kind? :name, "is too long" # => false
|
361
|
-
def of_kind?(attribute,
|
362
|
-
|
457
|
+
def of_kind?(attribute, type = :invalid)
|
458
|
+
attribute, type = normalize_arguments(attribute, type)
|
363
459
|
|
364
|
-
if
|
365
|
-
|
460
|
+
if type.is_a? Symbol
|
461
|
+
!where(attribute, type).empty?
|
366
462
|
else
|
367
|
-
|
463
|
+
messages_for(attribute).include?(type)
|
368
464
|
end
|
369
465
|
end
|
370
466
|
|
@@ -379,7 +475,7 @@ module ActiveModel
|
|
379
475
|
# person.errors.full_messages
|
380
476
|
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
|
381
477
|
def full_messages
|
382
|
-
map
|
478
|
+
@errors.map(&:full_message)
|
383
479
|
end
|
384
480
|
alias :to_a :full_messages
|
385
481
|
|
@@ -394,63 +490,28 @@ module ActiveModel
|
|
394
490
|
# person.errors.full_messages_for(:name)
|
395
491
|
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
|
396
492
|
def full_messages_for(attribute)
|
397
|
-
attribute
|
398
|
-
messages[attribute].map { |message| full_message(attribute, message) }
|
493
|
+
where(attribute).map(&:full_message).freeze
|
399
494
|
end
|
400
495
|
|
401
|
-
# Returns
|
496
|
+
# Returns all the error messages for a given attribute in an array.
|
402
497
|
#
|
403
|
-
#
|
498
|
+
# class Person
|
499
|
+
# validates_presence_of :name, :email
|
500
|
+
# validates_length_of :name, in: 5..30
|
501
|
+
# end
|
404
502
|
#
|
405
|
-
#
|
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)
|
508
|
+
end
|
509
|
+
|
510
|
+
# Returns a full message for a given attribute.
|
406
511
|
#
|
407
|
-
#
|
408
|
-
# * <tt>activemodel.errors.models.person/contacts/addresses.format</tt>
|
409
|
-
# * <tt>activemodel.errors.models.person.attributes.name.format</tt>
|
410
|
-
# * <tt>activemodel.errors.models.person.format</tt>
|
411
|
-
# * <tt>errors.format</tt>
|
512
|
+
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
|
412
513
|
def full_message(attribute, message)
|
413
|
-
|
414
|
-
attribute = attribute.to_s
|
415
|
-
|
416
|
-
if self.class.i18n_customize_full_message && @base.class.respond_to?(:i18n_scope)
|
417
|
-
attribute = attribute.remove(/\[\d\]/)
|
418
|
-
parts = attribute.split(".")
|
419
|
-
attribute_name = parts.pop
|
420
|
-
namespace = parts.join("/") unless parts.empty?
|
421
|
-
attributes_scope = "#{@base.class.i18n_scope}.errors.models"
|
422
|
-
|
423
|
-
if namespace
|
424
|
-
defaults = @base.class.lookup_ancestors.map do |klass|
|
425
|
-
[
|
426
|
-
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format",
|
427
|
-
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format",
|
428
|
-
]
|
429
|
-
end
|
430
|
-
else
|
431
|
-
defaults = @base.class.lookup_ancestors.map do |klass|
|
432
|
-
[
|
433
|
-
:"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format",
|
434
|
-
:"#{attributes_scope}.#{klass.model_name.i18n_key}.format",
|
435
|
-
]
|
436
|
-
end
|
437
|
-
end
|
438
|
-
|
439
|
-
defaults.flatten!
|
440
|
-
else
|
441
|
-
defaults = []
|
442
|
-
end
|
443
|
-
|
444
|
-
defaults << :"errors.format"
|
445
|
-
defaults << "%{attribute} %{message}"
|
446
|
-
|
447
|
-
attr_name = attribute.tr(".", "_").humanize
|
448
|
-
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
|
449
|
-
|
450
|
-
I18n.t(defaults.shift,
|
451
|
-
default: defaults,
|
452
|
-
attribute: attr_name,
|
453
|
-
message: message)
|
514
|
+
Error.full_message(attribute, message, @base)
|
454
515
|
end
|
455
516
|
|
456
517
|
# Translates an error message in its default scope
|
@@ -478,82 +539,129 @@ module ActiveModel
|
|
478
539
|
# * <tt>errors.attributes.title.blank</tt>
|
479
540
|
# * <tt>errors.messages.blank</tt>
|
480
541
|
def generate_message(attribute, type = :invalid, options = {})
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
542
|
+
Error.generate_message(attribute, type, @base, options)
|
543
|
+
end
|
544
|
+
|
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)
|
496
571
|
end
|
497
|
-
defaults << :"#{i18n_scope}.errors.messages.#{type}"
|
498
572
|
|
499
|
-
|
500
|
-
translation = I18n.translate(defaults.first, **options.merge(default: defaults.drop(1), throw: true))
|
501
|
-
return translation unless translation.nil?
|
502
|
-
end unless options[:message]
|
503
|
-
else
|
504
|
-
defaults = []
|
573
|
+
[attribute.to_sym, type, options]
|
505
574
|
end
|
506
575
|
|
507
|
-
|
508
|
-
|
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
|
509
584
|
|
510
|
-
|
511
|
-
|
512
|
-
|
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
|
513
593
|
|
514
|
-
|
515
|
-
|
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
|
516
598
|
|
517
|
-
|
518
|
-
|
599
|
+
class DeprecationHandlingMessageHash < SimpleDelegator
|
600
|
+
def initialize(errors)
|
601
|
+
@errors = errors
|
602
|
+
super(prepare_content)
|
519
603
|
end
|
520
604
|
|
521
|
-
def
|
522
|
-
|
523
|
-
|
524
|
-
|
605
|
+
def []=(attribute, value)
|
606
|
+
ActiveSupport::Deprecation.warn("Calling `[]=` to an ActiveModel::Errors is deprecated. Please call `ActiveModel::Errors#add` instead.")
|
607
|
+
|
608
|
+
@errors.delete(attribute)
|
609
|
+
Array(value).each do |message|
|
610
|
+
@errors.add(attribute, message)
|
611
|
+
end
|
612
|
+
|
613
|
+
__setobj__ prepare_content
|
525
614
|
end
|
526
615
|
|
527
|
-
def
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
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)
|
532
620
|
end
|
533
621
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
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
|
541
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)
|
542
643
|
end
|
543
644
|
|
544
|
-
def
|
545
|
-
|
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
|
546
651
|
end
|
547
652
|
|
548
|
-
def
|
549
|
-
|
550
|
-
|
551
|
-
|
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)
|
552
657
|
end
|
658
|
+
end
|
553
659
|
|
554
|
-
|
555
|
-
|
556
|
-
|
660
|
+
class DeprecationHandlingDetailsHash < SimpleDelegator
|
661
|
+
def initialize(details)
|
662
|
+
details.default = []
|
663
|
+
details.freeze
|
664
|
+
super(details)
|
557
665
|
end
|
558
666
|
end
|
559
667
|
|