From d5536406e516ee69dc8a24934b7129e4f45e2749 Mon Sep 17 00:00:00 2001 From: Gustavo Cunha Date: Sat, 21 Mar 2020 09:30:27 +0100 Subject: [PATCH] Add support to numericality: :other_than --- .../comparison_matcher.rb | 6 +- .../validate_numericality_of_matcher.rb | 32 +++++ .../validate_numericality_of_matcher_spec.rb | 126 ++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) diff --git a/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb b/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb index 8110a8329..43307be95 100644 --- a/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +++ b/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb @@ -9,7 +9,8 @@ class ComparisonMatcher < ValidationMatcher :>= => :greater_than_or_equal_to, :< => :less_than, :<= => :less_than_or_equal_to, - :== => :equal_to + :== => :equal_to, + :!= => :other_than, } def initialize(numericality_matcher, value, operator) @@ -125,6 +126,8 @@ def assertions [true, false, false] when :<= [true, true, false] + when :!= + [true, false, true] end end @@ -146,6 +149,7 @@ def comparison_expectation when :== then "equal to" when :< then "less than" when :<= then "less than or equal to" + when :!= then 'other than' end end end diff --git a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb index a4281be74..80484f79a 100644 --- a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb @@ -204,6 +204,33 @@ module ActiveModel # is_greater_than(21) # end # + # ##### is_other_than + # + # Use `is_other_than` to test usage of the `:other_than` option. + # This asserts that the attribute can take a number which is not equal to + # the given value. + # + # class Person + # include ActiveModel::Model + # attr_accessor :legal_age + # + # validates_numericality_of :legal_age, other_than: 21 + # end + # + # # RSpec + # RSpec.describe Person, type: :model do + # it do + # should validate_numericality_of(:legal_age). + # is_other_than(21) + # end + # end + # + # # Minitest (Shoulda) + # class PersonTest < ActiveSupport::TestCase + # should validate_numericality_of(:legal_age). + # is_other_than(21) + # end + # # ##### even # # Use `even` to test usage of the `:even` option. This asserts that the @@ -395,6 +422,11 @@ def is_less_than_or_equal_to(value) self end + def is_other_than(value) + prepare_submatcher(comparison_matcher_for(value, :!=).for(@attribute)) + self + end + def with_message(message) @expects_custom_validation_message = true @expected_message = message diff --git a/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb index 6d492f01d..84595afb0 100644 --- a/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb @@ -39,6 +39,13 @@ def all_qualifiers validation_name: :equal_to, validation_value: 1, }, + { + category: :comparison, + name: :is_other_than, + argument: 1, + validation_name: :other_than, + validation_value: 1, + }, { category: :cardinality, name: :odd, @@ -991,6 +998,125 @@ def configure_validation_matcher(matcher) end end + context 'qualified with is_other_than' do + context 'and validating with other_than' do + it 'accepts' do + record = build_record_validating_numericality(other_than: 18) + expect(record).to validate_numericality.is_other_than(18) + end + + it 'rejects when used in the negative' do + record = build_record_validating_numericality(other_than: 18) + + assertion = lambda do + expect(record).not_to validate_numericality.is_other_than(18) + end + + expect(&assertion).to fail_with_message(<<~MESSAGE) +Expected Example not to validate that :attr looks like a number other +than 18, but this could not be proved. + After setting :attr to ‹"abcd"›, the matcher expected the Example to + be valid, but it was invalid instead, producing these validation + errors: + + * attr: ["is not a number"] + MESSAGE + end + + it_supports( + 'ignoring_interference_by_writer', + tests: { + reject_if_qualified_but_changing_value_interferes: { + model_name: 'Example', + attribute_name: :attr, + changing_values_with: :next_value, + expected_message: <<-MESSAGE.strip +Expected Example to validate that :attr looks like a number other than +18, but this could not be proved. + After setting :attr to ‹"18"› -- which was read back as ‹"19"› -- the + matcher expected the Example to be invalid, but it was valid instead. + + As indicated in the message above, :attr seems to be changing certain + values as they are set, and this could have something to do with why + this test is failing. If you've overridden the writer method for this + attribute, then you may need to change it to make this test pass, or + do something else entirely. + MESSAGE + } + } + ) do + def validation_matcher_scenario_args + super.deep_merge(validation_options: { other_than: 18 }) + end + + def configure_validation_matcher(matcher) + matcher.is_other_than(18) + end + end + + context 'when the attribute is a virtual attribute in an ActiveRecord model' do + it 'accepts' do + record = build_record_validating_numericality_of_virtual_attribute( + other_than: 18, + ) + expect(record).to validate_numericality.is_other_than(18) + end + end + + context 'when the column is an integer column' do + it 'accepts (and does not raise an error)' do + record = build_record_validating_numericality( + column_type: :integer, + other_than: 18 + ) + + expect(record).to validate_numericality.is_other_than(18) + end + end + + context 'when the column is a float column' do + it 'accepts (and does not raise an error)' do + record = build_record_validating_numericality( + column_type: :float, + other_than: 18 + ) + + expect(record).to validate_numericality.is_other_than(18) + end + end + + context 'when the column is a decimal column' do + it 'accepts (and does not raise an error)' do + record = build_record_validating_numericality( + column_type: :decimal, + other_than: 18, + ) + + expect(record).to validate_numericality.is_other_than(18) + end + end + end + + context 'and not validating with other_than' do + it 'rejects since it does not disallow numbers that are not the value' do + record = build_record_validating_numericality + + assertion = lambda do + expect(record).to validate_numericality.is_other_than(18) + end + + message = <<-MESSAGE +Expected Example to validate that :attr looks like a number other than +18, but this could not be proved. + After setting :attr to ‹"18"›, the matcher expected the Example to be + invalid, but it was valid instead. + MESSAGE + + expect(&assertion).to fail_with_message(message) + end + end + end + context 'qualified with is_greater_than_or_equal to' do context 'validating with greater_than_or_equal_to' do it 'accepts' do