Use submatchers instead of inheritance for ValidatesNumericalityMatcher.

This commit is contained in:
Reade Harris 2012-10-16 13:45:06 -04:00 committed by Jason Draper
parent f5b5617663
commit 6ba8c9f1a1
7 changed files with 256 additions and 36 deletions

View File

@ -3,6 +3,8 @@ require 'shoulda/matchers/active_model/validation_matcher'
require 'shoulda/matchers/active_model/validation_message_finder'
require 'shoulda/matchers/active_model/exception_message_finder'
require 'shoulda/matchers/active_model/allow_value_matcher'
require 'shoulda/matchers/active_model/disallow_value_matcher'
require 'shoulda/matchers/active_model/only_integer_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'

View File

@ -0,0 +1,33 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
class DisallowValueMatcher # :nodoc:
def initialize(value)
@allow_matcher = Shoulda::Matchers::ActiveModel::AllowValueMatcher.new(value)
end
def matches?(subject)
!@allow_matcher.matches?(subject)
end
def for(attribute)
@allow_matcher.for(attribute)
self
end
def with_message(message)
@allow_matcher.with_message(message)
self
end
def failure_message
@allow_matcher.negative_failure_message
end
def allowed_types
""
end
end
end
end
end

View File

@ -0,0 +1,24 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
class OnlyIntegerMatcher # :nodoc:
def initialize(attribute)
@attribute = attribute
end
def matches?(subject)
matcher = AllowValueMatcher.new(0.1).for(@attribute)
!matcher.matches?(subject)
end
def with_message(message)
self
end
def allowed_types
"integer"
end
end
end
end
end

View File

@ -17,55 +17,68 @@ module Shoulda # :nodoc:
ValidateNumericalityOfMatcher.new(attr)
end
class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc:
class ValidateNumericalityOfMatcher
def initialize(attribute)
super(attribute)
@attribute = attribute
@options = {}
@submatchers = []
add_disallow_value_matcher
end
def only_integer
@options[:only_integer] = true
only_integer_matcher = OnlyIntegerMatcher.new(@attribute)
add_submatcher(only_integer_matcher)
self
end
def with_message(message)
if message
@expected_message = message
end
@expected_message = message
self
end
def matches?(subject)
super(subject)
disallows_non_integers? && disallows_text?
@subject = subject
set_expected_message_on_submatchers
submatchers_match?
end
def description
"only allow #{allowed_type} values for #{@attribute}"
"only allow #{allowed_types} values for #{@attribute}"
end
def failure_message
@disallow_value_matcher.failure_message
end
private
def allowed_type
if @options[:only_integer]
"integer"
else
"numeric"
end
def add_disallow_value_matcher
@disallow_value_matcher = DisallowValueMatcher.new('abcd').for(@attribute)
add_submatcher(@disallow_value_matcher)
end
def disallows_non_integers?
if @options[:only_integer]
message = @expected_message || :not_an_integer
disallows_value_of(0.1, message)
else
true
end
def add_submatcher(submatcher)
@submatchers << submatcher
end
def disallows_text?
def set_expected_message_on_submatchers
message = @expected_message || :not_a_number
disallows_value_of('abcd', message)
@submatchers.each { |matcher| matcher.with_message(message) }
end
def submatchers_match?
@submatchers.all? { |matcher| matcher.matches?(@subject) }
end
def allowed_types
allowed = ["numeric"] + submatcher_allowed_types
allowed.join(", ")
end
def submatcher_allowed_types
@submatchers.map(&:allowed_types).reject { |type| type.empty? }
end
end
end

View File

@ -0,0 +1,65 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher do
it "does not allow any types" do
matcher = Shoulda::Matchers::ActiveModel::DisallowValueMatcher.new("abcde")
matcher.allowed_types.should == ""
end
context "an attribute with a format validation" do
before do
define_model :example, :attr => :string do
validates_format_of :attr, :with => /abc/
end
@model = Example.new
end
it "does not match if the value is allowed" do
matcher = new_matcher("abcde")
matcher.for(:attr)
matcher.matches?(@model).should be_false
end
it "matches if the value is not allowed" do
matcher = new_matcher("xyz")
matcher.for(:attr)
matcher.matches?(@model).should be_true
end
end
context "an attribute with a format validation and a custom message" do
before do
define_model :example, :attr => :string do
validates_format_of :attr, :with => /abc/, :message => 'good message'
end
@model = Example.new
end
it "does not match if the value and message are both correct" do
matcher = new_matcher("abcde")
matcher.for(:attr).with_message('good message')
matcher.matches?(@model).should be_false
end
it "delegates its failure message to its allow matcher's negative failure message" do
allow_matcher = stub_everything(negative_failure_message: "allow matcher failure")
Shoulda::Matchers::ActiveModel::AllowValueMatcher.stubs(:new).returns(allow_matcher)
matcher = new_matcher("abcde")
matcher.for(:attr).with_message('good message')
matcher.matches?(@model)
matcher.failure_message.should == "allow matcher failure"
end
it "matches if the message is correct but the value is not" do
matcher = new_matcher("xyz")
matcher.for(:attr).with_message('good message')
matcher.matches?(@model).should be_true
end
end
def new_matcher(value)
matcher = Shoulda::Matchers::ActiveModel::DisallowValueMatcher.new(value)
end
end

View File

@ -0,0 +1,42 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do
context "given an attribute that only allows integer values" do
before do
define_model :example, :attr => :string do
validates_numericality_of :attr, { :only_integer => true }
end
@model = Example.new
end
it "matches" do
matcher = new_matcher(:attr)
matcher.matches?(@model).should be_true
end
it "allows integer types" do
matcher = new_matcher(:attr)
matcher.allowed_types.should == "integer"
end
it "returns itself when given a message" do
matcher = new_matcher(:attr)
matcher.with_message("some message").should == matcher
end
end
context "given an attribute that allows values other than integers" do
before do
@model = define_model(:example, :attr => :string).new
end
it "does not match" do
matcher = new_matcher(:attr)
matcher.matches?(@model).should be_false
end
end
def new_matcher(attribute)
matcher = Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher.new(attribute)
end
end

View File

@ -1,8 +1,12 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
it "should state in its description that it allows only numeric values" do
matcher = Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher.new(:attr)
matcher.description.should == "only allow numeric values for attr"
end
context "a numeric attribute" do
context "given a numeric attribute" do
before do
define_model :example, :attr => :string do
validates_numericality_of :attr
@ -11,28 +15,51 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
end
it "should only allow numeric values for that attribute" do
@model.should validate_numericality_of(:attr)
matcher = new_matcher(:attr)
matcher.matches?(@model).should be_true
end
it "should not override the default message with a blank" do
@model.should validate_numericality_of(:attr).with_message(nil)
matcher = new_matcher(:attr)
matcher.with_message(nil)
matcher.matches?(@model).should be_true
end
it "should not enforce integer values for that attribute" do
matcher = new_matcher(:attr)
matcher.only_integer
matcher.matches?(@model).should be_false
end
end
context "a numeric attribute which must be integer" do
context "given a numeric attribute which must be integer" do
before do
define_model :example, :attr => :string do
validates_numericality_of :attr, { :only_integer => true }
validates_numericality_of :attr, { :only_integer => true }
end
@model = Example.new
end
it "should only allow integer values for that attribute" do
@model.should validate_numericality_of(:attr).only_integer
it "allows integer values for that attribute" do
matcher = new_matcher(:attr)
matcher.only_integer
matcher.matches?(@model).should be_true
end
it "does not allow non-integer values for that attribute" do
matcher = new_matcher(:attr)
matcher.only_integer
matcher.matches?(@model).should be_true
end
it "should state in its description that it allows only integer values" do
matcher = new_matcher(:attr)
matcher.only_integer
matcher.description.should == "only allow numeric, integer values for attr"
end
end
context "a numeric attribute with a custom validation message" do
context "given a numeric attribute with a custom validation message" do
before do
define_model :example, :attr => :string do
validates_numericality_of :attr, :message => 'custom'
@ -41,22 +68,36 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
end
it "should only allow numeric values for that attribute with that message" do
@model.should validate_numericality_of(:attr).with_message(/custom/)
matcher = new_matcher(:attr)
matcher.with_message(/custom/)
matcher.matches?(@model).should be_true
end
it "should not allow numeric values for that attribute with another message" do
@model.should_not validate_numericality_of(:attr)
matcher = new_matcher(:attr)
matcher.with_message(/wrong/)
matcher.matches?(@model).should be_false
end
end
context "a non-numeric attribute" do
context "given a non-numeric attribute" do
before do
@model = define_model(:example, :attr => :string).new
end
it "should not only allow numeric values for that attribute" do
@model.should_not validate_numericality_of(:attr)
matcher = new_matcher(:attr)
matcher.matches?(@model).should be_false
end
it "should fail with the ActiveRecord :not_a_number message" do
matcher = new_matcher(:attr)
matcher.matches?(@model)
matcher.failure_message.should include 'Expected errors to include "is not a number"'
end
end
def new_matcher(attr)
Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher.new(attr)
end
end