mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add ActiveModel::Errors#details
To be able to return type of validator, one can now call `details` on Errors instance: ```ruby class User < ActiveRecord::Base validates :name, presence: true end ``` ```ruby user = User.new; user.valid?; user.errors.details => {name: [{error: :blank}]} ```
This commit is contained in:
parent
08fe700e2f
commit
cb74473db6
4 changed files with 154 additions and 23 deletions
|
@ -1,7 +1,25 @@
|
|||
* Add `ActiveModel::Errors#details`
|
||||
|
||||
To be able to return type of used validator, one can now call `details`
|
||||
on Errors instance:
|
||||
|
||||
```ruby
|
||||
class User < ActiveRecord::Base
|
||||
validates :name, presence: true
|
||||
end
|
||||
```
|
||||
|
||||
```ruby
|
||||
user = User.new; user.valid?; user.errors.details
|
||||
=> {name: [{error: :blank}]}
|
||||
```
|
||||
|
||||
*Wojciech Wnętrzak*
|
||||
|
||||
* Change validates_acceptance_of to accept true by default.
|
||||
|
||||
The default for validates_acceptance_of is now "1" and true.
|
||||
In the past, only "1" was the default and you were required to add
|
||||
In the past, only "1" was the default and you were required to add
|
||||
accept: true.
|
||||
|
||||
* Remove deprecated `ActiveModel::Dirty#reset_#{attribute}` and
|
||||
|
|
|
@ -23,7 +23,7 @@ module ActiveModel
|
|||
# attr_reader :errors
|
||||
#
|
||||
# def validate!
|
||||
# errors.add(:name, "cannot be nil") if name.nil?
|
||||
# errors.add(:name, :blank, message: "cannot be nil") if name.nil?
|
||||
# end
|
||||
#
|
||||
# # The following methods are needed to be minimally implemented
|
||||
|
@ -58,8 +58,9 @@ module ActiveModel
|
|||
include Enumerable
|
||||
|
||||
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
|
||||
MESSAGE_OPTIONS = [:message]
|
||||
|
||||
attr_reader :messages
|
||||
attr_reader :messages, :details
|
||||
|
||||
# Pass in the instance of the object that is using the errors object.
|
||||
#
|
||||
|
@ -71,10 +72,12 @@ module ActiveModel
|
|||
def initialize(base)
|
||||
@base = base
|
||||
@messages = {}
|
||||
@details = Hash.new { |details, attribute| details[attribute] = [] }
|
||||
end
|
||||
|
||||
def initialize_dup(other) # :nodoc:
|
||||
@messages = other.messages.dup
|
||||
@details = other.details.dup
|
||||
super
|
||||
end
|
||||
|
||||
|
@ -85,6 +88,7 @@ module ActiveModel
|
|||
# person.errors.full_messages # => []
|
||||
def clear
|
||||
messages.clear
|
||||
details.clear
|
||||
end
|
||||
|
||||
# Returns +true+ if the error messages include an error for the given key
|
||||
|
@ -126,6 +130,7 @@ module ActiveModel
|
|||
# person.errors.get(:name) # => nil
|
||||
def delete(key)
|
||||
messages.delete(key)
|
||||
details.delete(key)
|
||||
end
|
||||
|
||||
# When passed a symbol or a name of a method, returns an array of errors
|
||||
|
@ -149,12 +154,12 @@ module ActiveModel
|
|||
# Yields the attribute and the error for that attribute. If the attribute
|
||||
# has more than one error message, yields once for each error message.
|
||||
#
|
||||
# person.errors.add(:name, "can't be blank")
|
||||
# person.errors.add(:name, :blank, message: "can't be blank")
|
||||
# person.errors.each do |attribute, error|
|
||||
# # Will yield :name and "can't be blank"
|
||||
# end
|
||||
#
|
||||
# person.errors.add(:name, "must be specified")
|
||||
# person.errors.add(:name, :not_specified, message: "must be specified")
|
||||
# person.errors.each do |attribute, error|
|
||||
# # Will yield :name and "can't be blank"
|
||||
# # then yield :name and "must be specified"
|
||||
|
@ -167,9 +172,9 @@ module ActiveModel
|
|||
|
||||
# Returns the number of error messages.
|
||||
#
|
||||
# person.errors.add(:name, "can't be blank")
|
||||
# person.errors.add(:name, :blank, message: "can't be blank")
|
||||
# person.errors.size # => 1
|
||||
# person.errors.add(:name, "must be specified")
|
||||
# person.errors.add(:name, :not_specified, message: "must be specified")
|
||||
# person.errors.size # => 2
|
||||
def size
|
||||
values.flatten.size
|
||||
|
@ -193,8 +198,8 @@ module ActiveModel
|
|||
|
||||
# Returns an array of error messages, with the attribute name included.
|
||||
#
|
||||
# person.errors.add(:name, "can't be blank")
|
||||
# person.errors.add(:name, "must be specified")
|
||||
# person.errors.add(:name, :blank, message: "can't be blank")
|
||||
# person.errors.add(:name, :not_specified, message: "must be specified")
|
||||
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
|
||||
def to_a
|
||||
full_messages
|
||||
|
@ -202,9 +207,9 @@ module ActiveModel
|
|||
|
||||
# Returns the number of error messages.
|
||||
#
|
||||
# person.errors.add(:name, "can't be blank")
|
||||
# person.errors.add(:name, :blank, message: "can't be blank")
|
||||
# person.errors.count # => 1
|
||||
# person.errors.add(:name, "must be specified")
|
||||
# person.errors.add(:name, :not_specified, message: "must be specified")
|
||||
# person.errors.count # => 2
|
||||
def count
|
||||
to_a.size
|
||||
|
@ -223,8 +228,8 @@ module ActiveModel
|
|||
|
||||
# Returns an xml formatted representation of the Errors hash.
|
||||
#
|
||||
# person.errors.add(:name, "can't be blank")
|
||||
# person.errors.add(:name, "must be specified")
|
||||
# person.errors.add(:name, :blank, message: "can't be blank")
|
||||
# person.errors.add(:name, :not_specified, message: "must be specified")
|
||||
# person.errors.to_xml
|
||||
# # =>
|
||||
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
||||
|
@ -261,17 +266,20 @@ module ActiveModel
|
|||
end
|
||||
end
|
||||
|
||||
# Adds +message+ to the error messages on +attribute+. More than one error
|
||||
# can be added to the same +attribute+. If no +message+ is supplied,
|
||||
# <tt>:invalid</tt> is assumed.
|
||||
# Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
|
||||
# More than one error can be added to the same +attribute+.
|
||||
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
|
||||
#
|
||||
# person.errors.add(:name)
|
||||
# # => ["is invalid"]
|
||||
# person.errors.add(:name, 'must be implemented')
|
||||
# person.errors.add(:name, :not_implemented, message: "must be implemented")
|
||||
# # => ["is invalid", "must be implemented"]
|
||||
#
|
||||
# person.errors.messages
|
||||
# # => {:name=>["must be implemented", "is invalid"]}
|
||||
# # => {:name=>["is invalid", "must be implemented"]}
|
||||
#
|
||||
# person.errors.details
|
||||
# # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
|
||||
#
|
||||
# If +message+ is a symbol, it will be translated using the appropriate
|
||||
# scope (see +generate_message+).
|
||||
|
@ -293,16 +301,22 @@ module ActiveModel
|
|||
# +attribute+ should be set to <tt>:base</tt> if the error is not
|
||||
# directly associated with a single attribute.
|
||||
#
|
||||
# person.errors.add(:base, "either name or email must be present")
|
||||
# person.errors.add(:base, :name_or_email_blank,
|
||||
# message: "either name or email must be present")
|
||||
# person.errors.messages
|
||||
# # => {:base=>["either name or email must be present"]}
|
||||
# person.errors.details
|
||||
# # => {:base=>[{error: :name_or_email_blank}]}
|
||||
def add(attribute, message = :invalid, options = {})
|
||||
message = message.call if message.respond_to?(:call)
|
||||
detail = normalize_detail(attribute, message, options)
|
||||
message = normalize_message(attribute, message, options)
|
||||
if exception = options[:strict]
|
||||
exception = ActiveModel::StrictValidationFailed if exception == true
|
||||
raise exception, full_message(attribute, message)
|
||||
end
|
||||
|
||||
details[attribute.to_sym] << detail
|
||||
self[attribute] << message
|
||||
end
|
||||
|
||||
|
@ -339,6 +353,7 @@ module ActiveModel
|
|||
# person.errors.add :name, :blank
|
||||
# person.errors.added? :name, :blank # => true
|
||||
def added?(attribute, message = :invalid, options = {})
|
||||
message = message.call if message.respond_to?(:call)
|
||||
message = normalize_message(attribute, message, options)
|
||||
self[attribute].include? message
|
||||
end
|
||||
|
@ -447,12 +462,14 @@ module ActiveModel
|
|||
case message
|
||||
when Symbol
|
||||
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
|
||||
when Proc
|
||||
message.call
|
||||
else
|
||||
message
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_detail(attribute, message, options)
|
||||
{ error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
|
||||
end
|
||||
end
|
||||
|
||||
# Raised when a validation cannot be corrected by end users and are considered
|
||||
|
|
|
@ -323,4 +323,46 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' })
|
||||
person.errors.add_on_blank :name, message: 'custom'
|
||||
end
|
||||
|
||||
test "details returns added error detail" do
|
||||
person = Person.new
|
||||
person.errors.add(:name, :invalid)
|
||||
assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
|
||||
end
|
||||
|
||||
test "details returns added error detail with custom option" do
|
||||
person = Person.new
|
||||
person.errors.add(:name, :greater_than, count: 5)
|
||||
assert_equal({ name: [{ error: :greater_than, count: 5 }] }, person.errors.details)
|
||||
end
|
||||
|
||||
test "details do not include message option" do
|
||||
person = Person.new
|
||||
person.errors.add(:name, :invalid, message: "is bad")
|
||||
assert_equal({ name: [{ error: :invalid }] }, person.errors.details)
|
||||
end
|
||||
|
||||
test "dup duplicates details" do
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:name, :invalid)
|
||||
errors_dup = errors.dup
|
||||
errors_dup.add(:name, :taken)
|
||||
assert_not_same errors_dup.details, errors.details
|
||||
end
|
||||
|
||||
test "delete removes details on given attribute" do
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:name, :invalid)
|
||||
errors.delete(:name)
|
||||
assert_empty errors.details[:name]
|
||||
end
|
||||
|
||||
test "clear removes details" do
|
||||
person = Person.new
|
||||
person.errors.add(:name, :invalid)
|
||||
|
||||
assert_equal 1, person.errors.details.count
|
||||
person.errors.clear
|
||||
assert person.errors.details.empty?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -227,8 +227,26 @@ end
|
|||
```
|
||||
|
||||
We'll cover validation errors in greater depth in the [Working with Validation
|
||||
Errors](#working-with-validation-errors) section. For now, let's turn to the
|
||||
built-in validation helpers that Rails provides by default.
|
||||
Errors](#working-with-validation-errors) section.
|
||||
|
||||
### `errors.details`
|
||||
|
||||
To check what validator type was used on invalid attribute, you can use
|
||||
`errors.details[:attribute]`. It returns array of hashes where under `:error`
|
||||
key you will find symbol of used validator.
|
||||
|
||||
```ruby
|
||||
class Person < ActiveRecord::Base
|
||||
validates :name, presence: true
|
||||
end
|
||||
|
||||
>> person = Person.new
|
||||
>> person.valid?
|
||||
>> person.errors.details[:name] #=> [{error: :blank}]
|
||||
```
|
||||
|
||||
Using `details` with custom validators are covered in the [Working with
|
||||
Validation Errors](#working-with-validation-errors) section.
|
||||
|
||||
Validation Helpers
|
||||
------------------
|
||||
|
@ -1074,6 +1092,42 @@ Another way to do this is using `[]=` setter
|
|||
# => ["Name cannot contain the characters !@#%*()_-+="]
|
||||
```
|
||||
|
||||
### `errors.details`
|
||||
|
||||
You can add validator type to details hash when using `errors.add` method.
|
||||
|
||||
```ruby
|
||||
class Person < ActiveRecord::Base
|
||||
def a_method_used_for_validation_purposes
|
||||
errors.add(:name, :invalid_characters)
|
||||
end
|
||||
end
|
||||
|
||||
person = Person.create(name: "!@#")
|
||||
|
||||
person.errors.details[:name]
|
||||
# => [{error: :invalid_characters}]
|
||||
```
|
||||
|
||||
To improve error details to contain not allowed characters set, you can
|
||||
pass additional options to `errors.add` method.
|
||||
|
||||
```ruby
|
||||
class Person < ActiveRecord::Base
|
||||
def a_method_used_for_validation_purposes
|
||||
errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=")
|
||||
end
|
||||
end
|
||||
|
||||
person = Person.create(name: "!@#")
|
||||
|
||||
person.errors.details[:name]
|
||||
# => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}]
|
||||
```
|
||||
|
||||
All built in Rails validators populate details hash with corresponding
|
||||
validator types.
|
||||
|
||||
### `errors[:base]`
|
||||
|
||||
You can add error messages that are related to the object's state as a whole, instead of being related to a specific attribute. You can use this method when you want to say that the object is invalid, no matter the values of its attributes. Since `errors[:base]` is an array, you can simply add a string to it and it will be used as an error message.
|
||||
|
|
Loading…
Reference in a new issue