diff --git a/NEWS.md b/NEWS.md index b830fa2fb..a5724c769 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,15 @@ +# 3.0.0 + +### Backward-incompatible changes + +* The negative form of `allow_value` has been changed so that instead of + asserting that any of the given values is an invalid value (allowing good + values to pass through), assert that *all* values are invalid values (allowing + good values not to pass through). This means that this test which formerly + passed will now fail: + + expect(record).not_to allow_value('good value', *bad_values) + # 2.8.0 ### Deprecations diff --git a/lib/shoulda/matchers/active_model/allow_value_matcher.rb b/lib/shoulda/matchers/active_model/allow_value_matcher.rb index de540d81c..2afcab819 100644 --- a/lib/shoulda/matchers/active_model/allow_value_matcher.rb +++ b/lib/shoulda/matchers/active_model/allow_value_matcher.rb @@ -215,14 +215,12 @@ def _after_setting_value(&callback) def matches?(instance) self.instance = instance - validator.record = instance + values_to_match.all? { |value| value_matches?(value) } + end - values_to_match.none? do |value| - validator.reset - self.value = value - set_attribute(value) - errors_match? || any_range_error_occurred? - end + def does_not_match?(instance) + self.instance = instance + values_to_match.all? { |value| !value_matches?(value) } end def failure_message @@ -241,15 +239,26 @@ def description protected - attr_reader :attribute_to_check_message_against - attr_accessor :values_to_match, :instance, :attribute_to_set, :value, + attr_reader :instance, :attribute_to_check_message_against + attr_accessor :values_to_match, :attribute_to_set, :value, :matched_error, :after_setting_value_callback, :validator + def instance=(instance) + @instance = instance + validator.record = instance + end + def attribute_to_check_message_against=(attribute) @attribute_to_check_message_against = attribute validator.attribute = attribute end + def value_matches?(value) + self.value = value + set_attribute(value) + !(errors_match? || any_range_error_occurred?) + end + def set_attribute(value) set_attribute_ignoring_range_errors(value) after_setting_value_callback.call diff --git a/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb b/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb index c2cee49c2..68a1648ad 100644 --- a/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +++ b/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb @@ -165,24 +165,29 @@ less_than_or_equal_to: 50000 end.new end + bad_values = [nil, '', 'abc', '0', '50001', '123456', []] - it 'allows a good value' do + it 'matches given a good value' do expect(model).to allow_value('12345').for(:attr) end - bad_values.each do |bad_value| - it "rejects a bad value (#{bad_value.inspect})" do + it 'does not match given a bad value' do + bad_values.each do |bad_value| expect(model).not_to allow_value(bad_value).for(:attr) end end - it "rejects several bad values (#{bad_values.map(&:inspect).join(', ')})" do + it 'does not match given multiple bad values' do expect(model).not_to allow_value(*bad_values).for(:attr) end - it "rejects a mix of both good and bad values" do - expect(model).not_to allow_value('12345', *bad_values).for(:attr) + it "does not match given good values along with bad values" do + message = %{Expected errors when attr is set to "12345",\ngot no errors} + + expect { + expect(model).not_to allow_value('12345', *bad_values).for(:attr) + }.to fail_with_message(message) end end