From d5c26c43c0fb3311ec56e62a5fdfe9bf8bf69ecf Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Fri, 4 Jan 2019 20:35:17 +0200 Subject: [PATCH] Add `ActiveModel::Errors#of_kind?` Related to https://github.com/rails/rails/pull/34817#issuecomment-451508668 --- activemodel/CHANGELOG.md | 4 ++ activemodel/lib/active_model/errors.rb | 37 +++++++++--- activemodel/test/cases/errors_test.rb | 80 +++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 9 deletions(-) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 84567dcc18..f55d89a580 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add `ActiveModel::Errors#of_kind?`. + + *bogdanvlviv*, *Rafael Mendonça França* + * Fix numericality equality validation of `BigDecimal` and `Float` by casting to `BigDecimal` on both ends of the validation. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 969effdc20..9fd6f2d89c 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -328,15 +328,15 @@ module ActiveModel # person.errors.added? :name, :blank # => true # person.errors.added? :name, "can't be blank" # => true # - # If the error message requires an option, then it returns +true+ with - # the correct option, or +false+ with an incorrect or missing option. + # If the error message requires options, then it returns +true+ with + # the correct options, or +false+ with incorrect or missing options. # - # person.errors.add :name, :too_long, { count: 25 } - # person.errors.added? :name, :too_long, count: 25 # => true - # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true - # person.errors.added? :name, :too_long, count: 24 # => false - # person.errors.added? :name, :too_long # => false - # person.errors.added? :name, "is too long" # => false + # person.errors.add :name, :too_long, { count: 25 } + # person.errors.added? :name, :too_long, count: 25 # => true + # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true + # 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) @@ -347,6 +347,27 @@ module ActiveModel end end + # Returns +true+ if an error on the attribute with the given message is + # present, or +false+ otherwise. +message+ is treated the same as for +add+. + # + # person.errors.add :age + # person.errors.add :name, :too_long, { count: 25 } + # person.errors.of_kind? :age # => true + # person.errors.of_kind? :name # => false + # person.errors.of_kind? :name, :too_long # => true + # person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true + # 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) + + if message.is_a? Symbol + details[attribute.to_sym].map { |e| e[:error] }.include? message + else + self[attribute].include? message + end + end + # Returns all the full error messages in an array. # # class Person diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index f9015b869d..947f9bf99b 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -209,6 +209,8 @@ class ErrorsTest < ActiveModel::TestCase person.errors.add(:name, "cannot be blank") person.errors.add(:name, "is invalid") assert person.errors.added?(:name, "cannot be blank") + assert person.errors.added?(:name, "is invalid") + assert_not person.errors.added?(:name, "incorrect") end test "added? returns false when no errors are present" do @@ -222,7 +224,7 @@ class ErrorsTest < ActiveModel::TestCase assert_not person.errors.added?(:name, "cannot be blank") end - test "added? returns false when checking for an error, but not providing message arguments" do + test "added? returns false when checking for an error, but not providing message argument" do person = Person.new person.errors.add(:name, "cannot be blank") assert_not person.errors.added?(:name) @@ -233,6 +235,7 @@ class ErrorsTest < ActiveModel::TestCase person.errors.add :name, :too_long, count: 25 assert person.errors.added? :name, :too_long, count: 25 + assert person.errors.added? :name, "is too long (maximum is 25 characters)" assert_not person.errors.added? :name, :too_long, count: 24 assert_not person.errors.added? :name, :too_long assert_not person.errors.added? :name, "is too long" @@ -243,6 +246,81 @@ class ErrorsTest < ActiveModel::TestCase person = Person.new person.errors.add(:name, :wrong) assert_not person.errors.added?(:name, :used) + assert person.errors.added?(:name, :wrong) + end + + test "of_kind? returns false when checking for an error, but not providing message argument" do + person = Person.new + person.errors.add(:name, "cannot be blank") + assert_not person.errors.of_kind?(:name) + end + + test "of_kind? returns false when checking a nonexisting error and other errors are present for the given attribute" do + person = Person.new + person.errors.add(:name, "is invalid") + assert_not person.errors.of_kind?(:name, "cannot be blank") + end + + test "of_kind? returns false when no errors are present" do + person = Person.new + assert_not person.errors.of_kind?(:name) + end + + test "of_kind? matches the given message when several errors are present for the same attribute" do + person = Person.new + person.errors.add(:name, "cannot be blank") + person.errors.add(:name, "is invalid") + assert person.errors.of_kind?(:name, "cannot be blank") + assert person.errors.of_kind?(:name, "is invalid") + assert_not person.errors.of_kind?(:name, "incorrect") + end + + test "of_kind? defaults message to :invalid" do + person = Person.new + person.errors.add(:name) + assert person.errors.of_kind?(:name) + end + + test "of_kind? handles proc messages" do + person = Person.new + message = Proc.new { "cannot be blank" } + person.errors.add(:name, message) + assert person.errors.of_kind?(:name, message) + end + + test "of_kind? returns true when string attribute is used with a symbol message" do + person = Person.new + person.errors.add(:name, :blank) + assert person.errors.of_kind?("name", :blank) + end + + test "of_kind? handles symbol message" do + person = Person.new + person.errors.add(:name, :blank) + assert person.errors.of_kind?(:name, :blank) + end + + test "of_kind? detects indifferent if a specific error was added to the object" do + person = Person.new + person.errors.add(:name, "cannot be blank") + assert person.errors.of_kind?(:name, "cannot be blank") + assert person.errors.of_kind?("name", "cannot be blank") + end + + test "of_kind? ignores options" do + person = Person.new + person.errors.add :name, :too_long, count: 25 + + assert person.errors.of_kind? :name, :too_long + assert person.errors.of_kind? :name, "is too long (maximum is 25 characters)" + end + + test "of_kind? returns false when checking for an error by symbol and a different error with same message is present" do + I18n.backend.store_translations("en", errors: { attributes: { name: { wrong: "is wrong", used: "is wrong" } } }) + person = Person.new + person.errors.add(:name, :wrong) + assert_not person.errors.of_kind?(:name, :used) + assert person.errors.of_kind?(:name, :wrong) end test "size calculates the number of error messages" do