diff --git a/lib/shoulda/matchers/active_model/allow_value_matcher.rb b/lib/shoulda/matchers/active_model/allow_value_matcher.rb index 96853035a..fbb9e0618 100644 --- a/lib/shoulda/matchers/active_model/allow_value_matcher.rb +++ b/lib/shoulda/matchers/active_model/allow_value_matcher.rb @@ -2,7 +2,10 @@ module Shoulda # :nodoc: module Matchers module ActiveModel # :nodoc: - # Ensures that the attribute can be set to the given value. + # Ensures that the attribute can be set to the given value or values. If + # multiple values are given the match succeeds only if all given values + # are allowed. Otherwise, the matcher fails at the first bad value in the + # argument list (the remaining arguments are not processed then). # # Options: # * with_message - value the test expects to find in @@ -13,15 +16,16 @@ module ActiveModel # :nodoc: # it { should_not allow_value('bad').for(:isbn) } # it { should allow_value("isbn 1 2345 6789 0").for(:isbn) } # - def allow_value(value) - AllowValueMatcher.new(value) + def allow_value(*values) + raise ArgumentError.new("need at least one argument") if values.empty? + AllowValueMatcher.new(*values) end class AllowValueMatcher # :nodoc: include Helpers - def initialize(value) - @value = value + def initialize(*values) + @values_to_match = values end def for(attribute) @@ -39,8 +43,12 @@ def matches?(instance) if Symbol === @expected_message @expected_message = default_error_message(@expected_message, :model_name => @instance.class.to_s.underscore, :attribute => @attribute) end - @instance.send("#{@attribute}=", @value) - !errors_match? + @values_to_match.each do |value| + @value = value + @instance.send("#{@attribute}=", @value) + return false if errors_match? + end + true end def failure_message @@ -52,16 +60,25 @@ def negative_failure_message end def description - "allow #{@attribute} to be set to #{@value.inspect}" + "allow #{@attribute} to be set to " << + if @values_to_match.length > 1 + "any of [#{@values_to_match.map {|v| v.inspect }.join(', ')}]" + else + @values_to_match.first.inspect + end end private def errors_match? - @instance.valid? - @errors = errors_for_attribute(@instance, @attribute) - @errors = [@errors] unless @errors.is_a?(Array) - @expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?) + if ! @instance.valid? + @errors = errors_for_attribute(@instance, @attribute) + @errors = [@errors] unless @errors.is_a?(Array) + @expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?) + else + @errors = [] + false + end end def errors_for_attribute(instance, attribute) @@ -75,7 +92,6 @@ def errors_for_attribute(instance, attribute) def errors_match_regexp? if Regexp === @expected_message @matched_error = @errors.detect { |e| e =~ @expected_message } - !@matched_error.nil? else false end diff --git a/spec/shoulda/active_model/allow_value_matcher_spec.rb b/spec/shoulda/active_model/allow_value_matcher_spec.rb index f06de588e..cdb29ad8c 100644 --- a/spec/shoulda/active_model/allow_value_matcher_spec.rb +++ b/spec/shoulda/active_model/allow_value_matcher_spec.rb @@ -17,6 +17,14 @@ it "should not allow a bad value" do @model.should_not allow_value("xyz").for(:attr) end + + it "should allow several good values" do + @model.should allow_value("abcde", "deabc").for(:attr) + end + + it "should not allow several bad values" do + @model.should_not allow_value("xyz", "zyx", nil, []).for(:attr) + end end context "an attribute with a format validation and a custom message" do @@ -51,12 +59,31 @@ @model.should allow_value("12345").for(:attr) end - bad_values = [nil, "", "abc", "0", "50001", "123456"] + bad_values = [nil, "", "abc", "0", "50001", "123456", []] bad_values.each do |value| it "should not allow a bad value (#{value.inspect})" do @model.should_not allow_value(value).for(:attr) end end + + it "should not allow bad values (#{bad_values.map {|v| v.inspect}.join(', ')})" do + @model.should_not allow_value(*bad_values).for(:attr) + end end + context "an AllowValueMatcher with multiple values" do + before { @matcher = allow_value("foo", "bar").for(:baz) } + + it "should describe itself" do + @matcher.description.should eq('allow baz to be set to any of ["foo", "bar"]') + end + end + + context "an AllowValueMatcher with a single value" do + before { @matcher = allow_value("foo").for(:baz) } + + it "should describe itself" do + @matcher.description.should eq('allow baz to be set to "foo"') + end + end end