mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Change errors
Allow `each` to behave in new way if block arity is 1 Ensure dumped marshal from Rails 5 can be loaded Make errors compatible with marshal and YAML dumps from previous versions of Rails Add deprecation warnings Ensure each behave like the past, sorted by attribute
This commit is contained in:
parent
ef68d3e35c
commit
d9011e3935
4 changed files with 365 additions and 161 deletions
|
@ -8,7 +8,7 @@ module ActiveModel
|
|||
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
|
||||
MESSAGE_OPTIONS = [:message]
|
||||
|
||||
def initialize(base, attribute, type, **options)
|
||||
def initialize(base, attribute, type = :invalid, **options)
|
||||
@base = base
|
||||
@attribute = attribute
|
||||
@raw_type = type
|
||||
|
@ -56,5 +56,11 @@ module ActiveModel
|
|||
|
||||
true
|
||||
end
|
||||
|
||||
def strict_match?(attribute, type, **options)
|
||||
return false unless match?(attribute, type, **options)
|
||||
|
||||
full_message == Error.new(@base, attribute, type, **options).full_message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,10 @@ require "active_support/core_ext/array/conversions"
|
|||
require "active_support/core_ext/string/inflections"
|
||||
require "active_support/core_ext/object/deep_dup"
|
||||
require "active_support/core_ext/string/filters"
|
||||
require "active_support/deprecation"
|
||||
require "active_model/error"
|
||||
require "active_model/nested_error"
|
||||
require "forwardable"
|
||||
|
||||
module ActiveModel
|
||||
# == Active \Model \Errors
|
||||
|
@ -59,12 +63,17 @@ module ActiveModel
|
|||
class Errors
|
||||
include Enumerable
|
||||
|
||||
extend Forwardable
|
||||
def_delegators :@errors, :size, :clear, :blank?, :empty?, *(Enumerable.instance_methods(false) - [:to_a, :include?])
|
||||
|
||||
LEGACY_ATTRIBUTES = [:messages, :details].freeze
|
||||
|
||||
class << self
|
||||
attr_accessor :i18n_customize_full_message # :nodoc:
|
||||
end
|
||||
self.i18n_customize_full_message = false
|
||||
|
||||
attr_reader :messages, :details
|
||||
attr_reader :errors
|
||||
|
||||
# Pass in the instance of the object that is using the errors object.
|
||||
#
|
||||
|
@ -74,18 +83,17 @@ module ActiveModel
|
|||
# end
|
||||
# end
|
||||
def initialize(base)
|
||||
@base = base
|
||||
@messages = apply_default_array({})
|
||||
@details = apply_default_array({})
|
||||
@base = base
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def initialize_dup(other) # :nodoc:
|
||||
@messages = other.messages.dup
|
||||
@details = other.details.deep_dup
|
||||
@errors = other.errors.deep_dup
|
||||
super
|
||||
end
|
||||
|
||||
# Copies the errors from <tt>other</tt>.
|
||||
# For copying errors but keep <tt>@base</tt> as is.
|
||||
#
|
||||
# other - The ActiveModel::Errors instance.
|
||||
#
|
||||
|
@ -93,11 +101,26 @@ module ActiveModel
|
|||
#
|
||||
# person.errors.copy!(other)
|
||||
def copy!(other) # :nodoc:
|
||||
@messages = other.messages.dup
|
||||
@details = other.details.dup
|
||||
@errors = other.errors.deep_dup
|
||||
@errors.each { |error|
|
||||
error.instance_variable_set("@base", @base)
|
||||
}
|
||||
end
|
||||
|
||||
# Merges the errors from <tt>other</tt>.
|
||||
# Imports one error
|
||||
# Imported errors are wrapped as a NestedError,
|
||||
# providing access to original error object.
|
||||
# If attribute or type needs to be overriden, use `override_options`.
|
||||
#
|
||||
# override_options - Hash
|
||||
# @option override_options [Symbol] :attribute Override the attribute the error belongs to
|
||||
# @option override_options [Symbol] :type Override type of the error.
|
||||
def import(error, override_options = {})
|
||||
@errors.append(NestedError.new(@base, error, override_options))
|
||||
end
|
||||
|
||||
# Merges the errors from <tt>other</tt>,
|
||||
# each <tt>Error</tt> wrapped as <tt>NestedError</tt>.
|
||||
#
|
||||
# other - The ActiveModel::Errors instance.
|
||||
#
|
||||
|
@ -105,8 +128,9 @@ module ActiveModel
|
|||
#
|
||||
# person.errors.merge!(other)
|
||||
def merge!(other)
|
||||
@messages.merge!(other.messages) { |_, ary1, ary2| ary1 + ary2 }
|
||||
@details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }
|
||||
other.errors.each { |error|
|
||||
import(error)
|
||||
}
|
||||
end
|
||||
|
||||
# Removes all errors except the given keys. Returns a hash containing the removed errors.
|
||||
|
@ -116,18 +140,28 @@ module ActiveModel
|
|||
# person.errors.keys # => [:age, :gender]
|
||||
def slice!(*keys)
|
||||
keys = keys.map(&:to_sym)
|
||||
@details.slice!(*keys)
|
||||
@messages.slice!(*keys)
|
||||
|
||||
results = messages.slice!(*keys)
|
||||
|
||||
@errors.keep_if do |error|
|
||||
keys.include?(error.attribute)
|
||||
end
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
# Clear the error messages.
|
||||
# Search for errors matching +attribute+, +type+ or +options+.
|
||||
#
|
||||
# person.errors.full_messages # => ["name cannot be nil"]
|
||||
# person.errors.clear
|
||||
# person.errors.full_messages # => []
|
||||
def clear
|
||||
messages.clear
|
||||
details.clear
|
||||
# Only supplied params will be matched.
|
||||
#
|
||||
# person.errors.where(:name) # => all name errors.
|
||||
# person.errors.where(:name, :too_short) # => all name errors being too short
|
||||
# person.errors.where(:name, :too_short, minimum: 2) # => all name errors being too short and minimum is 2
|
||||
def where(attribute, type = nil, **options)
|
||||
attribute, type, options = normalize_arguments(attribute, type, options)
|
||||
@errors.select { |error|
|
||||
error.match?(attribute, type, options)
|
||||
}
|
||||
end
|
||||
|
||||
# Returns +true+ if the error messages include an error for the given key
|
||||
|
@ -137,8 +171,9 @@ module ActiveModel
|
|||
# person.errors.include?(:name) # => true
|
||||
# person.errors.include?(:age) # => false
|
||||
def include?(attribute)
|
||||
attribute = attribute.to_sym
|
||||
messages.key?(attribute) && messages[attribute].present?
|
||||
@errors.any? { |error|
|
||||
error.match?(attribute.to_sym)
|
||||
}
|
||||
end
|
||||
alias :has_key? :include?
|
||||
alias :key? :include?
|
||||
|
@ -148,10 +183,13 @@ module ActiveModel
|
|||
# person.errors[:name] # => ["cannot be nil"]
|
||||
# person.errors.delete(:name) # => ["cannot be nil"]
|
||||
# person.errors[:name] # => []
|
||||
def delete(key)
|
||||
attribute = key.to_sym
|
||||
details.delete(attribute)
|
||||
messages.delete(attribute)
|
||||
def delete(attribute, type = nil, **options)
|
||||
attribute, type, options = normalize_arguments(attribute, type, options)
|
||||
matches = where(attribute, type, options)
|
||||
matches.each do |error|
|
||||
@errors.delete(error)
|
||||
end
|
||||
matches.map(&:message)
|
||||
end
|
||||
|
||||
# When passed a symbol or a name of a method, returns an array of errors
|
||||
|
@ -160,7 +198,7 @@ module ActiveModel
|
|||
# person.errors[:name] # => ["cannot be nil"]
|
||||
# person.errors['name'] # => ["cannot be nil"]
|
||||
def [](attribute)
|
||||
messages[attribute.to_sym]
|
||||
where(attribute.to_sym).map { |error| error.message }
|
||||
end
|
||||
|
||||
# Iterates through each error key, value pair in the error messages hash.
|
||||
|
@ -177,31 +215,37 @@ module ActiveModel
|
|||
# # Will yield :name and "can't be blank"
|
||||
# # then yield :name and "must be specified"
|
||||
# end
|
||||
def each
|
||||
messages.each_key do |attribute|
|
||||
messages[attribute].each { |error| yield attribute, error }
|
||||
def each(&block)
|
||||
if block.arity == 1
|
||||
@errors.each(&block)
|
||||
else
|
||||
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||
Enumerating ActiveModel::Errors as a hash has been deprecated.
|
||||
In Rails 6, `errors` is an array of Error objects,
|
||||
therefore it should be accessed by a block with a single block
|
||||
parameter like this:
|
||||
|
||||
person.errors.each do |error|
|
||||
error.full_message
|
||||
end
|
||||
|
||||
You are passing a block expecting 2 parameters,
|
||||
so the old hash behavior is simulated. As this is deprecated,
|
||||
this will result in an ArgumentError in Rails 6.1.
|
||||
MSG
|
||||
@errors.
|
||||
sort { |a, b| a.attribute <=> b.attribute }.
|
||||
each { |error| yield error.attribute, error.message }
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the number of error messages.
|
||||
#
|
||||
# person.errors.add(:name, :blank, message: "can't be blank")
|
||||
# person.errors.size # => 1
|
||||
# person.errors.add(:name, :not_specified, message: "must be specified")
|
||||
# person.errors.size # => 2
|
||||
def size
|
||||
values.flatten.size
|
||||
end
|
||||
alias :count :size
|
||||
|
||||
# Returns all message values.
|
||||
#
|
||||
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
||||
# person.errors.values # => [["cannot be nil", "must be specified"]]
|
||||
def values
|
||||
messages.select do |key, value|
|
||||
!value.empty?
|
||||
end.values
|
||||
deprecation_removal_warning(:values)
|
||||
@errors.map(&:message).freeze
|
||||
end
|
||||
|
||||
# Returns all message keys.
|
||||
|
@ -209,21 +253,12 @@ module ActiveModel
|
|||
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
||||
# person.errors.keys # => [:name]
|
||||
def keys
|
||||
messages.select do |key, value|
|
||||
!value.empty?
|
||||
end.keys
|
||||
deprecation_removal_warning(:keys)
|
||||
keys = @errors.map(&:attribute)
|
||||
keys.uniq!
|
||||
keys.freeze
|
||||
end
|
||||
|
||||
# Returns +true+ if no errors are found, +false+ otherwise.
|
||||
# If the error message is a string it can be empty.
|
||||
#
|
||||
# person.errors.full_messages # => ["name cannot be nil"]
|
||||
# person.errors.empty? # => false
|
||||
def empty?
|
||||
size.zero?
|
||||
end
|
||||
alias :blank? :empty?
|
||||
|
||||
# Returns an xml formatted representation of the Errors hash.
|
||||
#
|
||||
# person.errors.add(:name, :blank, message: "can't be blank")
|
||||
|
@ -236,6 +271,7 @@ module ActiveModel
|
|||
# # <error>name must be specified</error>
|
||||
# # </errors>
|
||||
def to_xml(options = {})
|
||||
deprecation_removal_warning(:to_xml)
|
||||
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
|
||||
end
|
||||
|
||||
|
@ -255,13 +291,36 @@ module ActiveModel
|
|||
# person.errors.to_hash # => {:name=>["cannot be nil"]}
|
||||
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
|
||||
def to_hash(full_messages = false)
|
||||
if full_messages
|
||||
messages.each_with_object({}) do |(attribute, array), messages|
|
||||
messages[attribute] = array.map { |message| full_message(attribute, message) }
|
||||
hash = {}
|
||||
@errors.each do |error|
|
||||
if full_messages
|
||||
message = error.full_message
|
||||
else
|
||||
message = error.message
|
||||
end
|
||||
|
||||
if hash.has_key?(error.attribute)
|
||||
hash[error.attribute] << message
|
||||
else
|
||||
hash[error.attribute] = [message]
|
||||
end
|
||||
else
|
||||
without_default_proc(messages)
|
||||
end
|
||||
hash
|
||||
end
|
||||
alias :messages :to_hash
|
||||
|
||||
def details
|
||||
hash = {}
|
||||
@errors.each do |error|
|
||||
detail = error.detail
|
||||
|
||||
if hash.has_key?(error.attribute)
|
||||
hash[error.attribute] << detail
|
||||
else
|
||||
hash[error.attribute] = [detail]
|
||||
end
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
# Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
|
||||
|
@ -305,17 +364,20 @@ module ActiveModel
|
|||
# # => {: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(message, options)
|
||||
message = normalize_message(attribute, message, options)
|
||||
def add(attribute, type = :invalid, **options)
|
||||
error = Error.new(
|
||||
@base,
|
||||
*normalize_arguments(attribute, type, options)
|
||||
)
|
||||
|
||||
if exception = options[:strict]
|
||||
exception = ActiveModel::StrictValidationFailed if exception == true
|
||||
raise exception, full_message(attribute, message)
|
||||
raise exception, error.full_message
|
||||
end
|
||||
|
||||
details[attribute.to_sym] << detail
|
||||
messages[attribute.to_sym] << message
|
||||
@errors.append(error)
|
||||
|
||||
error
|
||||
end
|
||||
|
||||
# Returns +true+ if an error on the attribute with the given message is
|
||||
|
@ -334,13 +396,15 @@ module ActiveModel
|
|||
# person.errors.added? :name, :too_long, count: 24 # => false
|
||||
# person.errors.added? :name, :too_long # => false
|
||||
# person.errors.added? :name, "is too long" # => false
|
||||
def added?(attribute, message = :invalid, options = {})
|
||||
message = message.call if message.respond_to?(:call)
|
||||
def added?(attribute, type = :invalid, options = {})
|
||||
attribute, type, options = normalize_arguments(attribute, type, options)
|
||||
|
||||
if message.is_a? Symbol
|
||||
details[attribute.to_sym].include? normalize_detail(message, options)
|
||||
if type.is_a? Symbol
|
||||
@errors.any? { |error|
|
||||
error.strict_match?(attribute, type, options)
|
||||
}
|
||||
else
|
||||
self[attribute].include? message
|
||||
messages_for(attribute).include?(type)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -356,12 +420,12 @@ module ActiveModel
|
|||
# person.errors.of_kind? :name, :not_too_long # => false
|
||||
# person.errors.of_kind? :name, "is too long" # => false
|
||||
def of_kind?(attribute, message = :invalid)
|
||||
message = message.call if message.respond_to?(:call)
|
||||
attribute, message = normalize_arguments(attribute, message)
|
||||
|
||||
if message.is_a? Symbol
|
||||
details[attribute.to_sym].map { |e| e[:error] }.include? message
|
||||
!where(attribute, message).empty?
|
||||
else
|
||||
self[attribute].include? message
|
||||
messages_for(attribute).include?(message)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -376,7 +440,7 @@ module ActiveModel
|
|||
# person.errors.full_messages
|
||||
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
|
||||
def full_messages
|
||||
map { |attribute, message| full_message(attribute, message) }
|
||||
@errors.map(&:full_message)
|
||||
end
|
||||
alias :to_a :full_messages
|
||||
|
||||
|
@ -391,21 +455,16 @@ module ActiveModel
|
|||
# person.errors.full_messages_for(:name)
|
||||
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
|
||||
def full_messages_for(attribute)
|
||||
attribute = attribute.to_sym
|
||||
messages[attribute].map { |message| full_message(attribute, message) }
|
||||
where(attribute).map(&:full_message).freeze
|
||||
end
|
||||
|
||||
def messages_for(attribute)
|
||||
where(attribute).map(&:message).freeze
|
||||
end
|
||||
|
||||
# Returns a full message for a given attribute.
|
||||
#
|
||||
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
|
||||
#
|
||||
# The `"%{attribute} %{message}"` error format can be overridden with either
|
||||
#
|
||||
# * <tt>activemodel.errors.models.person/contacts/addresses.attributes.street.format</tt>
|
||||
# * <tt>activemodel.errors.models.person/contacts/addresses.format</tt>
|
||||
# * <tt>activemodel.errors.models.person.attributes.name.format</tt>
|
||||
# * <tt>activemodel.errors.models.person.format</tt>
|
||||
# * <tt>errors.format</tt>
|
||||
def full_message(attribute, message)
|
||||
return message if attribute == :base
|
||||
attribute = attribute.to_s
|
||||
|
@ -511,34 +570,50 @@ module ActiveModel
|
|||
I18n.translate(key, options)
|
||||
end
|
||||
|
||||
def marshal_dump # :nodoc:
|
||||
[@base, without_default_proc(@messages), without_default_proc(@details)]
|
||||
end
|
||||
|
||||
def marshal_load(array) # :nodoc:
|
||||
@base, @messages, @details = array
|
||||
apply_default_array(@messages)
|
||||
apply_default_array(@details)
|
||||
# Rails 5
|
||||
@errors = []
|
||||
@base = array[0]
|
||||
add_from_legacy_details_hash(array[2])
|
||||
end
|
||||
|
||||
def init_with(coder) # :nodoc:
|
||||
coder.map.each { |k, v| instance_variable_set(:"@#{k}", v) }
|
||||
@details ||= {}
|
||||
apply_default_array(@messages)
|
||||
apply_default_array(@details)
|
||||
data = coder.map
|
||||
|
||||
data.each { |k, v|
|
||||
next if LEGACY_ATTRIBUTES.include?(k.to_sym)
|
||||
instance_variable_set(:"@#{k}", v)
|
||||
}
|
||||
|
||||
@errors ||= []
|
||||
|
||||
# Legacy support Rails 5.x details hash
|
||||
add_from_legacy_details_hash(data["details"]) if data.key?("details")
|
||||
end
|
||||
|
||||
private
|
||||
def without_default_proc(hash)
|
||||
hash.dup.tap do |new_h|
|
||||
new_h.default_proc = nil
|
||||
private
|
||||
|
||||
def normalize_arguments(attribute, type, **options)
|
||||
# Evaluate proc first
|
||||
if type.respond_to?(:call)
|
||||
type = type.call(@base, options)
|
||||
end
|
||||
|
||||
[attribute.to_sym, type, options]
|
||||
end
|
||||
end
|
||||
|
||||
def apply_default_array(hash)
|
||||
hash.default_proc = proc { |h, key| h[key] = [] }
|
||||
hash
|
||||
end
|
||||
def add_from_legacy_details_hash(details)
|
||||
details.each { |attribute, errors|
|
||||
errors.each { |error|
|
||||
type = error.delete(:error)
|
||||
add(attribute, type, error)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def deprecation_removal_warning(method_name)
|
||||
ActiveSupport::Deprecation.warn("ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails 6.1")
|
||||
end
|
||||
end
|
||||
|
||||
# Raised when a validation cannot be corrected by end users and are considered
|
||||
|
|
|
@ -44,7 +44,6 @@ class ErrorTest < ActiveModel::TestCase
|
|||
test "initialize without type but with options" do
|
||||
options = { message: "bar" }
|
||||
error = ActiveModel::Error.new(Person.new, :name, options)
|
||||
assert_equal :invalid, error.type
|
||||
assert_equal(options, error.options)
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
@errors = ActiveModel::Errors.new(self)
|
||||
end
|
||||
|
||||
attr_accessor :name, :age
|
||||
attr_accessor :name, :age, :gender, :city
|
||||
attr_reader :errors
|
||||
|
||||
def validate!
|
||||
|
@ -31,48 +31,47 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
end
|
||||
|
||||
def test_delete
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors[:foo] << "omg"
|
||||
errors.delete("foo")
|
||||
assert_empty errors[:foo]
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:name, :blank)
|
||||
errors.delete("name")
|
||||
assert_empty errors[:name]
|
||||
end
|
||||
|
||||
def test_include?
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors[:foo] << "omg"
|
||||
assert_includes errors, :foo, "errors should include :foo"
|
||||
assert_includes errors, "foo", "errors should include 'foo' as :foo"
|
||||
end
|
||||
|
||||
def test_dup
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors[:foo] << "bar"
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:name)
|
||||
errors_dup = errors.dup
|
||||
errors_dup[:bar] << "omg"
|
||||
assert_not_same errors_dup.messages, errors.messages
|
||||
assert_not_same errors_dup.errors, errors.errors
|
||||
end
|
||||
|
||||
def test_has_key?
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors[:foo] << "omg"
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:foo, "omg")
|
||||
assert_equal true, errors.has_key?(:foo), "errors should have key :foo"
|
||||
assert_equal true, errors.has_key?("foo"), "errors should have key 'foo' as :foo"
|
||||
end
|
||||
|
||||
def test_has_no_key
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
assert_equal false, errors.has_key?(:name), "errors should not have key :name"
|
||||
end
|
||||
|
||||
def test_key?
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors[:foo] << "omg"
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:foo, "omg")
|
||||
assert_equal true, errors.key?(:foo), "errors should have key :foo"
|
||||
assert_equal true, errors.key?("foo"), "errors should have key 'foo' as :foo"
|
||||
end
|
||||
|
||||
def test_no_key
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
assert_equal false, errors.key?(:name), "errors should not have key :name"
|
||||
end
|
||||
|
||||
|
@ -86,42 +85,50 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
end
|
||||
|
||||
test "error access is indifferent" do
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors[:foo] << "omg"
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:name, "omg")
|
||||
|
||||
assert_equal ["omg"], errors["foo"]
|
||||
assert_equal ["omg"], errors["name"]
|
||||
end
|
||||
|
||||
test "values returns an array of messages" do
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.messages[:foo] = "omg"
|
||||
errors.messages[:baz] = "zomg"
|
||||
|
||||
assert_equal ["omg", "zomg"], errors.values
|
||||
assert_deprecated do
|
||||
assert_equal ["omg", "zomg"], errors.values
|
||||
end
|
||||
end
|
||||
|
||||
test "values returns an empty array after try to get a message only" do
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.messages[:foo]
|
||||
errors.messages[:baz]
|
||||
|
||||
assert_equal [], errors.values
|
||||
assert_deprecated do
|
||||
assert_equal [], errors.values
|
||||
end
|
||||
end
|
||||
|
||||
test "keys returns the error keys" do
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors.messages[:foo] << "omg"
|
||||
errors.messages[:baz] << "zomg"
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:name)
|
||||
errors.add(:age)
|
||||
|
||||
assert_equal [:foo, :baz], errors.keys
|
||||
assert_deprecated do
|
||||
assert_equal [:name, :age], errors.keys
|
||||
end
|
||||
end
|
||||
|
||||
test "keys returns an empty array after try to get a message only" do
|
||||
errors = ActiveModel::Errors.new(self)
|
||||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.messages[:foo]
|
||||
errors.messages[:baz]
|
||||
|
||||
assert_equal [], errors.keys
|
||||
assert_deprecated do
|
||||
assert_equal [], errors.keys
|
||||
end
|
||||
end
|
||||
|
||||
test "detecting whether there are errors with empty?, blank?, include?" do
|
||||
|
@ -146,30 +153,80 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
assert_equal ["cannot be nil"], person.errors[:name]
|
||||
end
|
||||
|
||||
test "add an error message on a specific attribute" do
|
||||
test "add creates an error object and returns it" do
|
||||
person = Person.new
|
||||
person.errors.add(:name, "cannot be blank")
|
||||
assert_equal ["cannot be blank"], person.errors[:name]
|
||||
error = person.errors.add(:name, :blank)
|
||||
|
||||
assert_equal :name, error.attribute
|
||||
assert_equal :blank, error.type
|
||||
assert_equal error, person.errors.first
|
||||
end
|
||||
|
||||
test "add an error message on a specific attribute with a defined type" do
|
||||
person = Person.new
|
||||
person.errors.add(:name, :blank, message: "cannot be blank")
|
||||
assert_equal ["cannot be blank"], person.errors[:name]
|
||||
end
|
||||
|
||||
test "add an error with a symbol" do
|
||||
test "add, with type as symbol" do
|
||||
person = Person.new
|
||||
person.errors.add(:name, :blank)
|
||||
message = person.errors.generate_message(:name, :blank)
|
||||
assert_equal [message], person.errors[:name]
|
||||
|
||||
assert_equal :blank, person.errors.first.type
|
||||
assert_equal ["can't be blank"], person.errors[:name]
|
||||
end
|
||||
|
||||
test "add an error with a proc" do
|
||||
test "add, with type as String" do
|
||||
msg = "custom msg"
|
||||
|
||||
person = Person.new
|
||||
message = Proc.new { "cannot be blank" }
|
||||
person.errors.add(:name, message)
|
||||
assert_equal ["cannot be blank"], person.errors[:name]
|
||||
person.errors.add(:name, msg)
|
||||
|
||||
assert_equal [msg], person.errors[:name]
|
||||
end
|
||||
|
||||
test "add, with type as nil" do
|
||||
person = Person.new
|
||||
person.errors.add(:name)
|
||||
|
||||
assert_equal :invalid, person.errors.first.type
|
||||
assert_equal ["is invalid"], person.errors[:name]
|
||||
end
|
||||
|
||||
test "add, with type as Proc, which evaluates to String" do
|
||||
msg = "custom msg"
|
||||
type = Proc.new { msg }
|
||||
|
||||
person = Person.new
|
||||
person.errors.add(:name, type)
|
||||
|
||||
assert_equal [msg], person.errors[:name]
|
||||
end
|
||||
|
||||
test "add, type being Proc, which evaluates to Symbol" do
|
||||
type = Proc.new { :blank }
|
||||
|
||||
person = Person.new
|
||||
person.errors.add(:name, type)
|
||||
|
||||
assert_equal :blank, person.errors.first.type
|
||||
assert_equal ["can't be blank"], person.errors[:name]
|
||||
end
|
||||
|
||||
test "initialize options[:message] as Proc, which evaluates to String" do
|
||||
msg = "custom msg"
|
||||
type = Proc.new { msg }
|
||||
|
||||
person = Person.new
|
||||
person.errors.add(:name, :blank, message: type)
|
||||
|
||||
assert_equal :blank, person.errors.first.type
|
||||
assert_equal [msg], person.errors[:name]
|
||||
end
|
||||
|
||||
test "add, with options[:message] as Proc, which evaluates to String, where type is nil" do
|
||||
msg = "custom msg"
|
||||
type = Proc.new { msg }
|
||||
|
||||
person = Person.new
|
||||
person.errors.add(:name, message: type)
|
||||
|
||||
assert_equal :invalid, person.errors.first.type
|
||||
assert_equal [msg], person.errors[:name]
|
||||
end
|
||||
|
||||
test "added? detects indifferent if a specific error was added to the object" do
|
||||
|
@ -449,7 +506,7 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
errors = ActiveModel::Errors.new(Person.new)
|
||||
errors.add(:name, :invalid)
|
||||
errors.delete(:name)
|
||||
assert_empty errors.details[:name]
|
||||
assert_not errors.added?(:name)
|
||||
end
|
||||
|
||||
test "delete returns the deleted messages" do
|
||||
|
@ -473,8 +530,10 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
person = Person.new
|
||||
person.errors.copy!(errors)
|
||||
|
||||
assert_equal [:name], person.errors.messages.keys
|
||||
assert_equal [:name], person.errors.details.keys
|
||||
assert person.errors.added?(:name, :invalid)
|
||||
person.errors.each do |error|
|
||||
assert_same person, error.base
|
||||
end
|
||||
end
|
||||
|
||||
test "merge errors" do
|
||||
|
@ -485,8 +544,8 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
person.errors.add(:name, :blank)
|
||||
person.errors.merge!(errors)
|
||||
|
||||
assert_equal({ name: ["can't be blank", "is invalid"] }, person.errors.messages)
|
||||
assert_equal({ name: [{ error: :blank }, { error: :invalid }] }, person.errors.details)
|
||||
assert(person.errors.added?(:name, :invalid))
|
||||
assert(person.errors.added?(:name, :blank))
|
||||
end
|
||||
|
||||
test "slice! removes all errors except the given keys" do
|
||||
|
@ -498,7 +557,9 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
|
||||
person.errors.slice!(:age, "gender")
|
||||
|
||||
assert_equal [:age, :gender], person.errors.keys
|
||||
assert_deprecated do
|
||||
assert_equal [:age, :gender], person.errors.keys
|
||||
end
|
||||
end
|
||||
|
||||
test "slice! returns the deleted errors" do
|
||||
|
@ -518,10 +579,23 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
errors.add(:name, :invalid)
|
||||
serialized = Marshal.load(Marshal.dump(errors))
|
||||
|
||||
assert_equal Person, serialized.instance_variable_get(:@base).class
|
||||
assert_equal errors.messages, serialized.messages
|
||||
assert_equal errors.details, serialized.details
|
||||
end
|
||||
|
||||
test "errors are compatible with marshal dumped from Rails 5.x" do
|
||||
# Derived from
|
||||
# errors = ActiveModel::Errors.new(Person.new)
|
||||
# errors.add(:name, :invalid)
|
||||
dump = "\x04\bU:\x18ActiveModel::Errors[\bo:\x17ErrorsTest::Person\x06:\f@errorsU;\x00[\b@\a{\x00{\x00{\x06:\tname[\x06I\"\x0Fis invalid\x06:\x06ET{\x06;\b[\x06{\x06:\nerror:\finvalid"
|
||||
serialized = Marshal.load(dump)
|
||||
|
||||
assert_equal Person, serialized.instance_variable_get(:@base).class
|
||||
assert_equal({ name: ["is invalid"] }, serialized.messages)
|
||||
assert_equal({ name: [{ error: :invalid }] }, serialized.details)
|
||||
end
|
||||
|
||||
test "errors are backward compatible with the Rails 4.2 format" do
|
||||
yaml = <<~CODE
|
||||
--- !ruby/object:ActiveModel::Errors
|
||||
|
@ -541,4 +615,54 @@ class ErrorsTest < ActiveModel::TestCase
|
|||
assert_equal({}, errors.messages)
|
||||
assert_equal({}, errors.details)
|
||||
end
|
||||
|
||||
test "errors are compatible with YAML dumped from Rails 5.x" do
|
||||
yaml = <<~CODE
|
||||
--- !ruby/object:ActiveModel::Errors
|
||||
base: &1 !ruby/object:ErrorsTest::Person
|
||||
errors: !ruby/object:ActiveModel::Errors
|
||||
base: *1
|
||||
messages: {}
|
||||
details: {}
|
||||
messages:
|
||||
:name:
|
||||
- is invalid
|
||||
details:
|
||||
:name:
|
||||
- :error: :invalid
|
||||
CODE
|
||||
|
||||
errors = YAML.load(yaml)
|
||||
assert_equal({ name: ["is invalid"] }, errors.messages)
|
||||
assert_equal({ name: [{ error: :invalid }] }, errors.details)
|
||||
|
||||
errors.clear
|
||||
assert_equal({}, errors.messages)
|
||||
assert_equal({}, errors.details)
|
||||
end
|
||||
|
||||
test "errors are compatible with YAML dumped from Rails 6.x" do
|
||||
yaml = <<~CODE
|
||||
--- !ruby/object:ActiveModel::Errors
|
||||
base: &1 !ruby/object:ErrorsTest::Person
|
||||
errors: !ruby/object:ActiveModel::Errors
|
||||
base: *1
|
||||
errors: []
|
||||
errors:
|
||||
- !ruby/object:ActiveModel::Error
|
||||
base: *1
|
||||
attribute: :name
|
||||
type: :invalid
|
||||
raw_type: :invalid
|
||||
options: {}
|
||||
CODE
|
||||
|
||||
errors = YAML.load(yaml)
|
||||
assert_equal({ name: ["is invalid"] }, errors.messages)
|
||||
assert_equal({ name: [{ error: :invalid }] }, errors.details)
|
||||
|
||||
errors.clear
|
||||
assert_equal({}, errors.messages)
|
||||
assert_equal({}, errors.details)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue