Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance AllowValueMatcher to take multiple arguments #80

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions lib/shoulda/matchers/active_model/allow_value_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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:
# * <tt>with_message</tt> - value the test expects to find in
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A cleaner way to do this is to do @errors = Array.wrap(@errors).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat.

@expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?)
else
@errors = []
false
end
end

def errors_for_attribute(instance, attribute)
Expand All @@ -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
Expand Down
29 changes: 28 additions & 1 deletion spec/shoulda/active_model/allow_value_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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