diff --git a/lib/shoulda/matchers/active_model.rb b/lib/shoulda/matchers/active_model.rb index 8fd6e11d..e77a633c 100644 --- a/lib/shoulda/matchers/active_model.rb +++ b/lib/shoulda/matchers/active_model.rb @@ -3,6 +3,7 @@ require 'shoulda/matchers/active_model/validation_matcher' require 'shoulda/matchers/active_model/allow_value_matcher' require 'shoulda/matchers/active_model/ensure_length_of_matcher' require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher' +require 'shoulda/matchers/active_model/ensure_exclusion_of_matcher' require 'shoulda/matchers/active_model/validate_presence_of_matcher' require 'shoulda/matchers/active_model/validate_format_of_matcher' require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher' diff --git a/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb b/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb new file mode 100644 index 00000000..b0456014 --- /dev/null +++ b/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb @@ -0,0 +1,70 @@ +module Shoulda # :nodoc: + module Matchers + module ActiveModel # :nodoc: + + # Ensure that the attribute's value is not in the range specified + # + # Options: + # * in_range - the range of not allowed values for this attribute + # * with_message - value the test expects to find in + # errors.on(:attribute). Regexp or string. Defaults to the + # translation for :exclusion. + # + # Example: + # it { should ensure_exclusion_of(:age).in_range(30..60) } + # + def ensure_exclusion_of(attr) + EnsureExclusionOfMatcher.new(attr) + end + + class EnsureExclusionOfMatcher < ValidationMatcher # :nodoc: + + def in_range(range) + @range = range + @minimum = range.first + @maximum = range.last + self + end + + def with_message(message) + @expected_message = message if message + self + end + + def description + "ensure exclusion of #{@attribute} in #{@range.inspect}" + end + + def matches?(subject) + super(subject) + + @expected_message ||= :exclusion + + allows_lower_value && + disallows_minimum_value && + allows_higher_value && + disallows_maximum_value + end + + private + + def allows_lower_value + @minimum == 0 || allows_value_of(@minimum - 1, @expected_message) + end + + def allows_higher_value + allows_value_of(@maximum + 1, @expected_message) + end + + def disallows_minimum_value + disallows_value_of(@minimum, @expected_message) + end + + def disallows_maximum_value + disallows_value_of(@maximum, @expected_message) + end + end + + end + end +end diff --git a/spec/shoulda/active_model/ensure_exclusion_of_matcher_spec.rb b/spec/shoulda/active_model/ensure_exclusion_of_matcher_spec.rb new file mode 100644 index 00000000..f3e17ac4 --- /dev/null +++ b/spec/shoulda/active_model/ensure_exclusion_of_matcher_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe Shoulda::Matchers::ActiveModel::EnsureExclusionOfMatcher do + + context "an attribute which must be excluded of a range" do + before do + @model = define_model(:example, :attr => :integer) do + validates_exclusion_of :attr, :in => 2..5 + end.new + end + + it "should accept ensuring the correct range" do + @model.should ensure_exclusion_of(:attr).in_range(2..5) + end + + it "should reject ensuring excluded value" do + @model.should_not ensure_exclusion_of(:attr).in_range(2..6) + end + + it "should not override the default message with a blank" do + @model.should ensure_exclusion_of(:attr).in_range(2..5).with_message(nil) + end + end + + context "an attribute with a custom ranged value validation" do + before do + @model = define_model(:example, :attr => :string) do + validates_exclusion_of :attr, :in => 2..4, :message => 'not good' + + end.new + end + + it "should accept ensuring the correct range" do + @model.should ensure_exclusion_of(:attr).in_range(2..4).with_message(/not good/) + end + end + + context "an attribute with custom range validations" do + before do + define_model :example, :attr => :integer do + validate :custom_validation + def custom_validation + if attr >= 2 && attr <= 5 + errors.add(:attr, 'shoud be out of this range') + end + end + end + @model = Example.new + end + + it "should accept ensuring the correct range and messages" do + @model.should ensure_exclusion_of(:attr).in_range(2..5).with_message(/shoud be out of this range/) + end + + end + +end