diff --git a/NEWS.md b/NEWS.md index 4df66abb9..0b27f1b51 100644 --- a/NEWS.md +++ b/NEWS.md @@ -70,6 +70,8 @@ * Add `strict` qualifier to `validate_numericality_of`. ([#620]) +* Add `on` qualifier to `validate_numericality_of`. (h/t [#356], [#358]) + [#402]: /~https://github.com/thoughtbot/shoulda-matchers/pull/402 [#587]: /~https://github.com/thoughtbot/shoulda-matchers/pull/587 [#662]: /~https://github.com/thoughtbot/shoulda-matchers/pull/662 @@ -82,6 +84,8 @@ [#677]: /~https://github.com/thoughtbot/shoulda-matchers/pull/677 [#620]: /~https://github.com/thoughtbot/shoulda-matchers/pull/620 [#693]: /~https://github.com/thoughtbot/shoulda-matchers/pull/693 +[#356]: /~https://github.com/thoughtbot/shoulda-matchers/pull/356 +[#358]: /~https://github.com/thoughtbot/shoulda-matchers/pull/358 # 2.8.0 diff --git a/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb b/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb index ca79d6e12..04ecc4ddf 100644 --- a/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +++ b/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb @@ -22,6 +22,11 @@ def strict self end + def on(context) + @disallow_value_matcher.on(context) + self + end + def allowed_type raise NotImplementedError 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 99fc96100..571a7ff96 100644 --- a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb @@ -384,6 +384,11 @@ def with_message(message) self end + def on(context) + @submatchers.each { |matcher| matcher.on(context) } + self + end + def matches?(subject) @subject = subject failing_submatchers.empty? diff --git a/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb index 649b5db34..addb3c1c8 100644 --- a/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb @@ -197,6 +197,26 @@ def matcher_qualifier end end + context 'qualified with on and validating with on' do + it 'accepts' do + expect(instance_with_validations(on: :customizable)). + to matcher.on(:customizable) + end + end + + context 'qualified with on but not validating with on' do + it 'accepts since the validation never considers a context' do + expect(instance_with_validations).to matcher.on(:customizable) + end + end + + context 'not qualified with on but validating with on' do + it 'rejects since the validation never runs' do + expect(instance_with_validations(on: :customizable)). + not_to matcher + end + end + describe '#comparison_description' do [{ operator: :>, value: 0, expectation: 'greater than 0' }, { operator: :>=, value: -1.0, expectation: 'greater than or equal to -1.0' }, diff --git a/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb index fe528c19d..94244dc61 100644 --- a/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb @@ -64,6 +64,26 @@ end end + context 'qualified with on and validating with on' do + it 'accepts' do + expect(validating_even_number(on: :customizable)). + to subject.on(:customizable) + end + end + + context 'qualified with on but not validating with on' do + it 'accepts since the validation never considers a context' do + expect(validating_even_number).to subject.on(:customizable) + end + end + + context 'not qualified with on but validating with on' do + it 'rejects since the validation never runs' do + expect(validating_even_number(on: :customizable)). + not_to subject + end + end + def validating_even_number(options = {}) define_model :example, attr: :string do validates_numericality_of :attr, { even: true }.merge(options) diff --git a/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb index 167ca8898..fe071c6fa 100644 --- a/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb @@ -64,6 +64,26 @@ end end + context 'qualified with on and validating with on' do + it 'accepts' do + expect(validating_odd_number(on: :customizable)). + to subject.on(:customizable) + end + end + + context 'qualified with on but not validating with on' do + it 'accepts since the validation never considers a context' do + expect(validating_odd_number).to subject.on(:customizable) + end + end + + context 'not qualified with on but validating with on' do + it 'rejects since the validation never runs' do + expect(validating_odd_number(on: :customizable)). + not_to subject + end + end + def validating_odd_number(options = {}) define_model :example, attr: :string do validates_numericality_of :attr, { odd: true }.merge(options) diff --git a/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb index c833a0e81..d365a3366 100644 --- a/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb @@ -64,6 +64,26 @@ end end + context 'qualified with on and validating with on' do + it 'accepts' do + expect(validating_only_integer(on: :customizable)). + to subject.on(:customizable) + end + end + + context 'qualified with on but not validating with on' do + it 'accepts since the validation never considers a context' do + expect(validating_only_integer).to subject.on(:customizable) + end + end + + context 'not qualified with on but validating with on' do + it 'rejects since the validation never runs' do + expect(validating_only_integer(on: :customizable)). + not_to subject + end + end + def validating_only_integer(options = {}) define_model :example, attr: :string do validates_numericality_of :attr, { only_integer: true }.merge(options) 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 6d01aa255..8e4c9549c 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 @@ -55,6 +55,12 @@ def all_qualifiers name: :only_integer, validation_name: :only_integer, validation_value: true, + }, + { + name: :on, + argument: :customizable, + validation_name: :on, + validation_value: :customizable } ] end @@ -454,6 +460,32 @@ def default_validation_values end end + context 'qualified with on and validating with on' do + it 'accepts' do + record = build_record_validating_numericality(on: :customizable) + expect(record).to validate_numericality.on(:customizable) + end + end + + context 'qualified with on but not validating with on' do + it 'accepts since the validation never considers a context' do + record = build_record_validating_numericality + expect(record).to validate_numericality.on(:customizable) + end + end + + context 'not qualified with on but validating with on' do + it 'rejects since the validation never runs' do + record = build_record_validating_numericality(on: :customizable) + assertion = lambda do + expect(record).to validate_numericality + end + expect(&assertion).to fail_with_message_including( + 'Expected errors to include "is not a number"' + ) + end + end + context 'with combinations of qualifiers together' do all_qualifier_combinations.each do |combination| if combination.size > 1 @@ -778,7 +810,8 @@ def apply_qualifiers!(args) end def define_model_validating_numericality(options = {}) - attribute_name = options.fetch(:attribute_name) { self.attribute_name } + attribute_name = options.delete(:attribute_name) { self.attribute_name } + define_model 'Example', attribute_name => :string do |model| model.validates_numericality_of(attribute_name, options) end