From 8aa860dc2d91890e89929fe0f40b1a4a91e1b6c1 Mon Sep 17 00:00:00 2001 From: Abd ar-Rahman Date: Tue, 19 Mar 2013 09:50:39 +0500 Subject: [PATCH] Add odd & even number matchers for numericality matcher --- NEWS.md | 1 + lib/shoulda/matchers/active_model.rb | 1 + .../active_model/odd_even_number_matcher.rb | 43 +++++++++ .../validate_numericality_of_matcher.rb | 17 +++- .../odd_even_number_matcher_spec.rb | 93 +++++++++++++++++++ .../active_model/only_integer_matcher_spec.rb | 8 +- .../validate_numericality_of_matcher_spec.rb | 52 +++++++++++ 7 files changed, 210 insertions(+), 5 deletions(-) create mode 100644 lib/shoulda/matchers/active_model/odd_even_number_matcher.rb create mode 100644 spec/shoulda/matchers/active_model/odd_even_number_matcher_spec.rb diff --git a/NEWS.md b/NEWS.md index 8f6f827e..27c1c486 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # HEAD +* Add `:odd` and `:even` options to the `validate_numericality_of` matcher. * Add `:touch` option to the association matcher. * Ruby 2.0.0 is now officially supported. * Fixes the issue where using %{attribute} or %{model} in I18n translations diff --git a/lib/shoulda/matchers/active_model.rb b/lib/shoulda/matchers/active_model.rb index 8745862a..2d950bf0 100644 --- a/lib/shoulda/matchers/active_model.rb +++ b/lib/shoulda/matchers/active_model.rb @@ -5,6 +5,7 @@ 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/odd_even_number_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' diff --git a/lib/shoulda/matchers/active_model/odd_even_number_matcher.rb b/lib/shoulda/matchers/active_model/odd_even_number_matcher.rb new file mode 100644 index 00000000..c84dcdea --- /dev/null +++ b/lib/shoulda/matchers/active_model/odd_even_number_matcher.rb @@ -0,0 +1,43 @@ +module Shoulda # :nodoc: + module Matchers + module ActiveModel # :nodoc: + 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 + end + + def matches?(subject) + @disallow_value_matcher.matches?(subject) + end + + def with_message(message) + @disallow_value_matcher.with_message(message) + self + end + + def allowed_types + 'integer' + end + + def failure_message_for_should + @disallow_value_matcher.failure_message_for_should + end + end + end + end +end \ No newline at end of file diff --git a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb index 8d2c249b..743eb648 100644 --- a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb @@ -8,10 +8,14 @@ module Shoulda # :nodoc: # errors.on(:attribute). Regexp or string. Defaults to the # translation for :not_a_number. # * only_integer - allows only integer values + # * odd - Specifies the value must be an odd number. + # * even - Specifies the value must be an even number. # # Examples: # it { should validate_numericality_of(:price) } # it { should validate_numericality_of(:age).only_integer } + # it { should validate_numericality_of(:frequency).odd } + # it { should validate_numericality_of(:frequency).even } # def validate_numericality_of(attr) ValidateNumericalityOfMatcher.new(attr) @@ -22,7 +26,6 @@ module Shoulda # :nodoc: def initialize(attribute) @attribute = attribute - @options = {} @submatchers = [] add_disallow_value_matcher @@ -34,6 +37,18 @@ module Shoulda # :nodoc: self end + def odd + odd_number_matcher = OddEvenNumberMatcher.new(@attribute, :odd => true) + add_submatcher(odd_number_matcher) + self + end + + def even + even_number_matcher = OddEvenNumberMatcher.new(@attribute, :even => true) + add_submatcher(even_number_matcher) + self + end + def with_message(message) @submatchers.each { |matcher| matcher.with_message(message) } self diff --git a/spec/shoulda/matchers/active_model/odd_even_number_matcher_spec.rb b/spec/shoulda/matchers/active_model/odd_even_number_matcher_spec.rb new file mode 100644 index 00000000..8c18cfbb --- /dev/null +++ b/spec/shoulda/matchers/active_model/odd_even_number_matcher_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe Shoulda::Matchers::ActiveModel::OddEvenNumberMatcher do + context 'given an attribute that only allows odd number values' do + it 'matches' do + validating_odd_number.should new_odd_matcher + end + + it 'returns itself when given a message' do + matcher = new_odd_matcher + matcher.with_message('some message').should == matcher + end + end + + context 'given an attribute that only allows even number values' do + it 'matches' do + validating_even_number.should new_even_matcher + end + + it 'returns itself when given a message' do + matcher = new_even_matcher + matcher.with_message('some message').should == 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 + validating_odd_number(:message => 'custom').should new_odd_matcher.with_message(/custom/) + end + + it 'rejects odd number values for that attribute with another message' do + validating_odd_number(:message => 'custom').should_not 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 + validating_even_number(:message => 'custom').should new_even_matcher.with_message(/custom/) + end + + it 'rejects even number values for that attribute with another message' do + validating_even_number(:message => 'custom').should_not new_even_matcher.with_message(/wrong/) + end + end + + context 'when the model does not have an odd validation' do + it 'does not match' do + define_model(:example, :attr => :string).new.should_not new_odd_matcher + end + + it 'fails with the ActiveRecord :odd message' do + matcher = new_odd_matcher + + matcher.matches?(define_model(:example, :attr => :string).new) + + matcher.failure_message_for_should.should 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 + define_model(:example, :attr => :string).new.should_not new_even_matcher + end + + it 'fails with the ActiveRecord :even message' do + matcher = new_even_matcher + + matcher.matches?(define_model(:example, :attr => :string).new) + + matcher.failure_message_for_should.should 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 diff --git a/spec/shoulda/matchers/active_model/only_integer_matcher_spec.rb b/spec/shoulda/matchers/active_model/only_integer_matcher_spec.rb index e0ac9177..dec23ed5 100644 --- a/spec/shoulda/matchers/active_model/only_integer_matcher_spec.rb +++ b/spec/shoulda/matchers/active_model/only_integer_matcher_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do context 'given an attribute that only allows integer values' do it 'matches' do - only_integer.should new_matcher + validating_only_integer.should new_matcher end it 'allows integer types' do @@ -18,11 +18,11 @@ describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do 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 - only_integer(:message => 'custom').should new_matcher.with_message(/custom/) + validating_only_integer(:message => 'custom').should new_matcher.with_message(/custom/) end it 'rejects integer values for that attribute with another message' do - only_integer(:message => 'custom').should_not new_matcher.with_message(/wrong/) + validating_only_integer(:message => 'custom').should_not new_matcher.with_message(/wrong/) end end @@ -44,7 +44,7 @@ describe Shoulda::Matchers::ActiveModel::OnlyIntegerMatcher do described_class.new(:attr) end - def only_integer(options = {}) + def validating_only_integer(options = {}) define_model :example, :attr => :string do validates_numericality_of :attr, { :only_integer => true }.merge(options) end.new diff --git a/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb b/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb index f9b9af65..a1da0412 100644 --- a/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +++ b/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb @@ -29,6 +29,22 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do the_matcher.failure_message_for_should.should include '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) + + the_matcher.failure_message_for_should.should include '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) + + the_matcher.failure_message_for_should.should include 'Expected errors to include "must be even"' + end end context 'with the only_integer option' do @@ -49,6 +65,42 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do end end + context 'with the odd option' do + it 'allows odd number values for that attribute' do + validating_numericality(:odd => true).should matcher.odd + end + + it 'rejects when the model does not enforce odd number values' do + validating_numericality.should_not matcher.odd + end + + it 'rejects with the ActiveRecord :odd message' do + the_matcher = matcher.odd + + the_matcher.matches?(validating_numericality) + + the_matcher.failure_message_for_should.should include 'Expected errors to include "must be odd"' + end + end + + context 'with the even option' do + it 'allows even number values for that attribute' do + validating_numericality(:even => true).should matcher.even + end + + it 'rejects when the model does not enforce even number values' do + validating_numericality.should_not matcher.even + end + + it 'rejects with the ActiveRecord :even message' do + the_matcher = matcher.even + + the_matcher.matches?(validating_numericality) + + the_matcher.failure_message_for_should.should include 'Expected errors to include "must be even"' + end + end + context 'with a custom validation message' do it 'accepts when the messages match' do validating_numericality(:message => 'custom').