1
0
Fork 0
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:
Wojciech Wnętrzak 2015-01-04 09:18:03 +01:00
parent 08fe700e2f
commit cb74473db6
4 changed files with 154 additions and 23 deletions

View file

@ -1,3 +1,21 @@
* 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.

View file

@ -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

View file

@ -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

View file

@ -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.