diff --git a/NEWS.md b/NEWS.md
index 59cfecc0..46b61254 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -30,6 +30,9 @@
* Additionally, we now manage Appraisals using the `appraisal` executable that
is coming in Appraisal 1.0.0 (we're using the beta for now).
+* Add `allow_nil` option to `validate_numericality_of` so that you can validate
+ that numeric values are validated only if a value is supplied.
+
# v 2.5.0
* Fix Rails/Test::Unit integration to ensure that the test case classes we are
diff --git a/README.md b/README.md
index 959c9249..331d86b0 100644
--- a/README.md
+++ b/README.md
@@ -448,6 +448,7 @@ class Person < ActiveRecord::Base
validates_numericality_of :birth_year, less_than_or_equal_to: 1987
validates_numericality_of :birth_day, odd: true
validates_numericality_of :birth_month, even: true
+ validates_numericality_of :rank, less_than_or_equal_to: 10, allow_nil: true
validates_numericality_of :number_of_dependents,
message: 'Number of dependents must be a number'
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 954279c5..3c5ff013 100644
--- a/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb
+++ b/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb
@@ -10,12 +10,14 @@ module Shoulda # :nodoc:
# * only_integer - allows only integer values
# * odd - Specifies the value must be an odd number.
# * even - Specifies the value must be an even number.
+ # * allow_nil - allows nil values
#
# 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 }
+ # it { should validate_numericality_of(:rank).less_than_or_equal_to(10).allow_nil }
#
def validate_numericality_of(attr)
ValidateNumericalityOfMatcher.new(attr)
@@ -37,6 +39,11 @@ module Shoulda # :nodoc:
self
end
+ def allow_nil
+ add_submatcher(AllowValueMatcher.new(nil).for(@attribute).with_message(:not_a_number))
+ self
+ end
+
def is_greater_than(value)
add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :>).for(@attribute))
self
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 2a3cefe6..61006f8a 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
@@ -47,6 +47,19 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher do
end
end
+ context 'with the allow_nil option' do
+ it 'allows nil values for that attribute' do
+ expect(validating_numericality(allow_nil: true)).to matcher.allow_nil
+ end
+
+ it 'rejects when the model does not allow nil' do
+ the_matcher = matcher.allow_nil
+ expect {
+ expect(validating_numericality).to the_matcher
+ }.to fail_with_message_including('Did not expect errors to include "is not a number"')
+ end
+ end
+
context 'with the only_integer option' do
it 'allows integer values for that attribute' do
expect(validating_numericality(only_integer: true)).to matcher.only_integer