Improve ValidateNumericalityOfMatcher document expression

This commit is contained in:
Yukio Mizuta 2014-01-12 17:43:36 -08:00 committed by Elliot Winkler
parent 6d1da0093f
commit 806716c79c
18 changed files with 294 additions and 219 deletions

View File

@ -9,6 +9,10 @@
non-password attribute which is in a model that `has_secure_password`. Doing
so previously would result in a "Password digest missing on new record" error.
* Fix description for `validate_numericality_of` so that if the matcher fails,
the error message reported does not say the matcher accepts integer values if
you didn't specify that.
# v 2.5.0
* Fix Rails/Test::Unit integration to ensure that the test case classes we are

View File

@ -13,8 +13,10 @@ require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
require 'shoulda/matchers/active_model/validate_confirmation_of_matcher'
require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/comparison_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/odd_even_number_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/odd_number_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/even_number_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/only_integer_matcher'
require 'shoulda/matchers/active_model/allow_mass_assignment_of_matcher'
require 'shoulda/matchers/active_model/errors'

View File

@ -35,10 +35,6 @@ module Shoulda # :nodoc:
end
alias failure_message_for_should_not failure_message_when_negated
def allowed_types
''
end
def strict
@allow_matcher.strict
self

View File

@ -23,14 +23,14 @@ module Shoulda # :nodoc:
disallows_value_of(value_to_compare, @message)
end
def allowed_types
'integer'
end
def with_message(message)
@message = message
end
def comparison_description
"#{expectation} #{@value}"
end
private
def value_to_compare

View File

@ -0,0 +1,22 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
module NumericalityMatchers
class EvenNumberMatcher < NumericTypeMatcher # :nodoc:
NON_EVEN_NUMBER_VALUE = 1
def initialize(attribute, options = {})
@attribute = attribute
@disallow_value_matcher = DisallowValueMatcher.new(NON_EVEN_NUMBER_VALUE).
for(@attribute).
with_message(:even)
end
def allowed_type
'even numbers'
end
end
end
end
end
end

View File

@ -2,24 +2,9 @@ module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
module NumericalityMatchers
class OddEvenNumberMatcher # :nodoc:
NON_EVEN_NUMBER_VALUE = 1
NON_ODD_NUMBER_VALUE = 2
def initialize(attribute, options = {})
@attribute = attribute
options[:odd] ||= true
options[:even] ||= false
if options[:odd] && !options[:even]
@disallow_value_matcher = DisallowValueMatcher.new(NON_ODD_NUMBER_VALUE).
for(@attribute).
with_message(:odd)
else
@disallow_value_matcher = DisallowValueMatcher.new(NON_EVEN_NUMBER_VALUE).
for(@attribute).
with_message(:even)
end
class NumericTypeMatcher
def initialize
raise NotImplementedError
end
def matches?(subject)
@ -31,8 +16,8 @@ module Shoulda # :nodoc:
self
end
def allowed_types
'integer'
def allowed_type
raise NotImplementedError
end
def failure_message

View File

@ -0,0 +1,22 @@
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
module NumericalityMatchers
class OddNumberMatcher < NumericTypeMatcher # :nodoc:
NON_ODD_NUMBER_VALUE = 2
def initialize(attribute, options = {})
@attribute = attribute
@disallow_value_matcher = DisallowValueMatcher.new(NON_ODD_NUMBER_VALUE).
for(@attribute).
with_message(:odd)
end
def allowed_type
'odd numbers'
end
end
end
end
end
end

View File

@ -2,9 +2,8 @@ module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
module NumericalityMatchers
class OnlyIntegerMatcher # :nodoc:
class OnlyIntegerMatcher < NumericTypeMatcher # :nodoc:
NON_INTEGER_VALUE = 0.1
def initialize(attribute)
@attribute = attribute
@disallow_value_matcher = DisallowValueMatcher.new(NON_INTEGER_VALUE).
@ -12,28 +11,9 @@ module Shoulda # :nodoc:
with_message(:not_an_integer)
end
def matches?(subject)
@disallow_value_matcher.matches?(subject)
def allowed_type
'integers'
end
def with_message(message)
@disallow_value_matcher.with_message(message)
self
end
def allowed_types
'integer'
end
def failure_message
@disallow_value_matcher.failure_message
end
alias failure_message_for_should failure_message
def failure_message_when_negated
@disallow_value_matcher.failure_message_when_negated
end
alias failure_message_for_should_not failure_message_when_negated
end
end
end

View File

@ -22,6 +22,7 @@ module Shoulda # :nodoc:
end
class ValidateNumericalityOfMatcher
NUMERIC_NAME = 'numbers'
NON_NUMERIC_VALUE = 'abcd'
def initialize(attribute)
@ -62,13 +63,13 @@ module Shoulda # :nodoc:
end
def odd
odd_number_matcher = NumericalityMatchers::OddEvenNumberMatcher.new(@attribute, odd: true)
odd_number_matcher = NumericalityMatchers::OddNumberMatcher.new(@attribute)
add_submatcher(odd_number_matcher)
self
end
def even
even_number_matcher = NumericalityMatchers::OddEvenNumberMatcher.new(@attribute, even: true)
even_number_matcher = NumericalityMatchers::EvenNumberMatcher.new(@attribute)
add_submatcher(even_number_matcher)
self
end
@ -84,7 +85,7 @@ module Shoulda # :nodoc:
end
def description
"only allow #{allowed_types} values for #{@attribute}"
"only allow #{allowed_types} for #{@attribute}#{comparison_descriptions}"
end
def failure_message
@ -128,12 +129,21 @@ module Shoulda # :nodoc:
end
def allowed_types
allowed = ['numeric'] + submatcher_allowed_types
allowed.join(', ')
allowed_array = submatcher_allowed_types
allowed_array.empty? ? NUMERIC_NAME : allowed_array.join(', ')
end
def submatcher_allowed_types
@submatchers.map(&:allowed_types).reject(&:empty?)
@submatchers.inject([]){|m, s| m << s.allowed_type if s.respond_to?(:allowed_type); m }
end
def comparison_descriptions
description_array = submatcher_comparison_descriptions
description_array.empty? ? '' : ' which are ' + submatcher_comparison_descriptions.join(' and ')
end
def submatcher_comparison_descriptions
@submatchers.inject([]){|m, s| m << s.comparison_description if s.respond_to?(:comparison_description); m }
end
end
end

View File

@ -1,10 +1,6 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher do
it 'does not allow any types' do
expect(matcher('abcde').allowed_types).to eq ''
end
context 'an attribute with a format validation' do
it 'does not match if the value is allowed' do
expect(validating_format(with: /abc/)).not_to matcher('abcde').for(:attr)

View File

@ -37,6 +37,20 @@ describe Shoulda::Matchers::ActiveModel::NumericalityMatchers::ComparisonMatcher
end
end
describe '#comparison_description' do
[{ operator: :>, value: 0, expectation: 'greater than 0' },
{ operator: :>=, value: -1.0, expectation: 'greater than or equal to -1.0' },
{ operator: :==, value: 2.2, expectation: 'equal to 2.2' },
{ operator: :<, value: -3, expectation: 'less than -3' },
{ operator: :<=, value: 4, expectation: 'less than or equal to 4' },
].each do |h|
context "with :#{h[:operator]} as operator and #{h[:value]} as value" do
subject { described_class.new(h[:value], h[:operator]).comparison_description }
it { should eq h[:expectation] }
end
end
end
def instance_with_validations(options = {})
define_model :example, attr: :string do
validates_numericality_of :attr, options

View File

@ -0,0 +1,55 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::NumericalityMatchers::EvenNumberMatcher do
subject { described_class.new(:attr) }
it_behaves_like 'a numerical submatcher'
it_behaves_like 'a numerical type submatcher'
it 'allows even number' do
expect(subject.allowed_type).to eq 'even numbers'
end
context 'when the model has an even validation' do
it 'matches' do
match = subject
expect(validating_even_number).to match
end
end
context 'when the model does not have an even validation' do
it 'does not match' do
match = subject
expect(not_validating_even_number).not_to match
end
it 'fails with the ActiveRecord :even message' do
match = subject
expect {
expect(not_validating_even_number).to match
}.to fail_with_message_including('Expected errors to include "must be even"')
end
end
context 'with custom validation message' do
it 'only accepts even number values for that attribute with that message' do
expect(validating_even_number(message: 'custom')).to subject.with_message(/custom/)
end
it 'fails even number values for that attribute with another message' do
expect(validating_even_number(message: 'custom')).not_to subject.with_message(/wrong/)
end
end
def validating_even_number(options = {})
define_model :example, attr: :string do
validates_numericality_of :attr, { even: true }.merge(options)
end.new
end
def not_validating_even_number
define_model(:example, attr: :string).new
end
end

View File

@ -1,97 +0,0 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::NumericalityMatchers::OddEvenNumberMatcher do
it_behaves_like 'a numerical submatcher' do
subject { described_class.new(:attr) }
end
context 'given an attribute that only allows odd number values' do
it 'matches' do
expect(validating_odd_number).to new_odd_matcher
end
it 'returns itself when given a message' do
matcher = new_odd_matcher
expect(matcher.with_message('some message')).to eq matcher
end
end
context 'given an attribute that only allows even number values' do
it 'matches' do
expect(validating_even_number).to new_even_matcher
end
it 'returns itself when given a message' do
matcher = new_even_matcher
expect(matcher.with_message('some message')).to eq matcher
end
end
context 'given an attribute that only allows odd number values with a custom validation message' do
it 'only accepts odd number values for that attribute with that message' do
expect(validating_odd_number(message: 'custom')).to new_odd_matcher.with_message(/custom/)
end
it 'rejects odd number values for that attribute with another message' do
expect(validating_odd_number(message: 'custom')).not_to new_odd_matcher.with_message(/wrong/)
end
end
context 'given an attribute that only allows even number values with a custom validation message' do
it 'only accepts even number values for that attribute with that message' do
expect(validating_even_number(message: 'custom')).to new_even_matcher.with_message(/custom/)
end
it 'rejects even number values for that attribute with another message' do
expect(validating_even_number(message: 'custom')).not_to new_even_matcher.with_message(/wrong/)
end
end
context 'when the model does not have an odd validation' do
it 'does not match' do
expect(define_model(:example, attr: :string).new).not_to new_odd_matcher
end
it 'fails with the ActiveRecord :odd message' do
matcher = new_odd_matcher
matcher.matches?(define_model(:example, attr: :string).new)
expect(matcher.failure_message).to include 'Expected errors to include "must be odd"'
end
end
context 'when the model does not have an even validation' do
it 'does not match' do
expect(define_model(:example, attr: :string).new).not_to new_even_matcher
end
it 'fails with the ActiveRecord :even message' do
matcher = new_even_matcher
matcher.matches?(define_model(:example, attr: :string).new)
expect(matcher.failure_message).to include 'Expected errors to include "must be even"'
end
end
def new_odd_matcher
described_class.new(:attr, odd: true)
end
def new_even_matcher
described_class.new(:attr, even: true)
end
def validating_odd_number(options = {})
define_model :example, attr: :string do
validates_numericality_of :attr, { odd: true }.merge(options)
end.new
end
def validating_even_number(options = {})
define_model :example, attr: :string do
validates_numericality_of :attr, { even: true }.merge(options)
end.new
end
end

View File

@ -0,0 +1,55 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::NumericalityMatchers::OddNumberMatcher do
subject { described_class.new(:attr) }
it_behaves_like 'a numerical submatcher'
it_behaves_like 'a numerical type submatcher'
it 'allows odd number' do
expect(subject.allowed_type).to eq 'odd numbers'
end
context 'when the model has an odd validation' do
it 'matches' do
match = subject
expect(validating_odd_number).to match
end
end
context 'when the model does not have an odd validation' do
it 'does not match' do
match = subject
expect(not_validating_odd_number).not_to match
end
it 'fails with the ActiveRecord :odd message' do
match = subject
expect {
expect(not_validating_odd_number).to match
}.to fail_with_message_including('Expected errors to include "must be odd"')
end
end
context 'with custom validation message' do
it 'only accepts odd number values for that attribute with that message' do
expect(validating_odd_number(message: 'custom')).to subject.with_message(/custom/)
end
it 'fails odd number values for that attribute with another message' do
expect(validating_odd_number(message: 'custom')).not_to subject.with_message(/wrong/)
end
end
def validating_odd_number(options = {})
define_model :example, attr: :string do
validates_numericality_of :attr, { odd: true }.merge(options)
end.new
end
def not_validating_odd_number
define_model(:example, attr: :string).new
end
end

View File

@ -1,56 +1,53 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::NumericalityMatchers::OnlyIntegerMatcher do
it_behaves_like 'a numerical submatcher' do
subject { described_class.new(:attr) }
subject { described_class.new(:attr) }
it_behaves_like 'a numerical submatcher'
it_behaves_like 'a numerical type submatcher'
it 'allows integer types' do
expect(subject.allowed_type).to eq 'integers'
end
context 'given an attribute that only allows integer values' do
it 'matches' do
expect(validating_only_integer).to new_matcher
end
it 'allows integer types' do
expect(new_matcher.allowed_types).to eq 'integer'
end
it 'returns itself when given a message' do
matcher = new_matcher
expect(matcher.with_message('some message')).to eq matcher
match = subject
expect(validating_only_integer).to match
end
end
context 'given an attribute that only allows integer values with a custom validation message' do
it 'only accepts integer values for that attribute with that message' do
expect(validating_only_integer(message: 'custom')).to new_matcher.with_message(/custom/)
expect(validating_only_integer(message: 'custom')).to subject.with_message(/custom/)
end
it 'rejects integer values for that attribute with another message' do
expect(validating_only_integer(message: 'custom')).not_to new_matcher.with_message(/wrong/)
expect(validating_only_integer(message: 'custom')).not_to subject.with_message(/wrong/)
end
end
context 'when the model does not have an only_integer validation' do
it 'does not match' do
expect(define_model(:example, attr: :string).new).not_to new_matcher
match = subject
expect(not_validating_only_integer).not_to match
end
it 'fails with the ActiveRecord :not_an_integer message' do
matcher = new_matcher
matcher.matches?(define_model(:example, attr: :string).new)
expect(matcher.failure_message).to include 'Expected errors to include "must be an integer"'
match = subject
expect {
expect(not_validating_only_integer).to match
}.to fail_with_message_including('Expected errors to include "must be an integer"')
end
end
def new_matcher
described_class.new(:attr)
end
def validating_only_integer(options = {})
define_model :example, attr: :string do
validates_numericality_of :attr, { only_integer: true }.merge(options)
end.new
end
def not_validating_only_integer
define_model(:example, attr: :string).new
end
end

View File

@ -1,11 +1,6 @@
require 'spec_helper'
describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
context '#description' do
it 'states that it allows only numeric values' do
expect(matcher.description).to eq 'only allow numeric values for attr'
end
end
context 'with a model with a numericality validation' do
it 'accepts' do
@ -19,7 +14,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
context 'with a model without a numericality validation' do
it 'rejects' do
expect(define_model(:example, attr: :string).new).not_to matcher
expect(not_validating_numericality).not_to matcher
end
it 'rejects with the ActiveRecord :not_a_number message' do
@ -32,26 +27,23 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
it 'rejects with the ActiveRecord :not_an_integer message' do
the_matcher = matcher.only_integer
the_matcher.matches?(define_model(:example, attr: :string).new)
expect(the_matcher.failure_message).to include 'Expected errors to include "must be an integer"'
expect {
expect(not_validating_numericality).to the_matcher
}.to fail_with_message_including('Expected errors to include "must be an integer"')
end
it 'rejects with the ActiveRecord :odd message' do
the_matcher = matcher.odd
the_matcher.matches?(define_model(:example, attr: :string).new)
expect(the_matcher.failure_message).to include 'Expected errors to include "must be odd"'
expect {
expect(not_validating_numericality).to the_matcher
}.to fail_with_message_including('Expected errors to include "must be odd"')
end
it 'rejects with the ActiveRecord :even message' do
the_matcher = matcher.even
the_matcher.matches?(define_model(:example, attr: :string).new)
expect(the_matcher.failure_message).to include 'Expected errors to include "must be even"'
expect {
expect(not_validating_numericality).to the_matcher
}.to fail_with_message_including('Expected errors to include "must be even"')
end
end
@ -66,10 +58,9 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
it 'rejects with the ActiveRecord :not_an_integer message' do
the_matcher = matcher.only_integer
the_matcher.matches?(validating_numericality)
expect(the_matcher.failure_message).to include 'Expected errors to include "must be an integer"'
expect {
expect(validating_numericality).to the_matcher
}.to fail_with_message_including('Expected errors to include "must be an integer"')
end
end
@ -84,10 +75,9 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
it 'rejects with the ActiveRecord :odd message' do
the_matcher = matcher.odd
the_matcher.matches?(validating_numericality)
expect(the_matcher.failure_message).to include 'Expected errors to include "must be odd"'
expect {
expect(validating_numericality).to the_matcher
}.to fail_with_message_including('Expected errors to include "must be odd"')
end
end
@ -102,22 +92,21 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
it 'rejects with the ActiveRecord :even message' do
the_matcher = matcher.even
the_matcher.matches?(validating_numericality)
expect(the_matcher.failure_message).to include 'Expected errors to include "must be even"'
expect {
expect(validating_numericality).to the_matcher
}.to fail_with_message_including('Expected errors to include "must be even"')
end
end
context 'with a custom validation message' do
it 'accepts when the messages match' do
expect(validating_numericality(message: 'custom')).
to matcher.with_message(/custom/)
to matcher.with_message(/custom/)
end
it 'rejects when the messages do not match' do
expect(validating_numericality(message: 'custom')).
not_to matcher.with_message(/wrong/)
not_to matcher.with_message(/wrong/)
end
end
@ -134,12 +123,51 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
end
end
describe '#description' do
context 'without submatchers' do
it { expect(matcher.description).to eq 'only allow numbers for attr' }
end
context 'with only integer option' do
it { expect(matcher.only_integer.description).to eq 'only allow integers for attr' }
end
[:odd, :even].each do |type|
context "with #{type} option" do
it { expect(matcher.__send__(type).description).to eq "only allow #{type} numbers for attr" }
end
end
[:is_greater_than, :is_greater_than_or_equal_to, :is_less_than, :is_less_than_or_equal_to,
:is_equal_to ].each do |comparison|
context "with #{comparison} option" do
it { expect(matcher.__send__(comparison, 18).description).
to eq "only allow numbers for attr which are #{comparison.to_s.sub('is_','').gsub('_', ' ')} 18" }
end
end
context 'with odd, is_greater_than_or_equal_to option' do
it { expect(matcher.odd.is_greater_than_or_equal_to(18).description).
to eq "only allow odd numbers for attr which are greater than or equal to 18" }
end
context 'with only integer, is_greater_than and less_than_or_equal_to option' do
it { expect(matcher.only_integer.is_greater_than(18).is_less_than_or_equal_to(100).description).
to eq "only allow integers for attr which are greater than 18 and less than or equal to 100" }
end
end
def validating_numericality(options = {})
define_model :example, attr: :string do
validates_numericality_of :attr, options
end.new
end
def not_validating_numericality
define_model(:example, attr: :string).new
end
def matcher
validate_numericality_of(:attr)
end

View File

@ -5,10 +5,6 @@ shared_examples 'a numerical submatcher' do
expect(subject).to respond_to(:with_message).with(1).arguments
end
it 'implements the allowed_types method' do
expect(subject).to respond_to(:allowed_types).with(0).arguments
end
it 'implements the matches? method' do
expect(subject).to respond_to(:matches?).with(1).arguments
end

View File

@ -0,0 +1,10 @@
require 'spec_helper'
shared_examples 'a numerical type submatcher' do
it 'implements the allowed_type method' do
expect(subject).to respond_to(:allowed_type).with(0).arguments
end
it 'returns itself when given a message' do
expect(subject.with_message('some message')).to eq subject
end
end