Merge pull request #18322 from morgoth/add-error-codes

Add ActiveModel::Errors#codes
This commit is contained in:
Rafael Mendonça França 2015-01-21 14:28:54 -02:00
commit 14599a5758
4 changed files with 154 additions and 23 deletions

View File

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

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.