diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index c7871c4ed8..166b29df47 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -11,7 +11,7 @@ module ActiveModel
RANGE_CHECKS = { in: :in? }
NUMBER_CHECKS = { odd: :odd?, even: :even? }
- RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer]
+ RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer, :only_numeric]
INTEGER_REGEX = /\A[+-]?\d+\z/
@@ -90,6 +90,10 @@ module ActiveModel
end
def is_number?(raw_value, precision, scale)
+ if options[:only_numeric] && !raw_value.is_a?(Numeric)
+ return false
+ end
+
!parse_as_number(raw_value, precision, scale).nil?
rescue ArgumentError, TypeError
false
@@ -162,6 +166,9 @@ module ActiveModel
# * :message - A custom error message (default is: "is not a number").
# * :only_integer - Specifies whether the value has to be an
# integer (default is +false+).
+ # * :only_numeric - Specifies whether the value has to be an
+ # instance of Numeric (default is +false+). The default behavior is to
+ # attempt parsing the value if it is a String.
# * :allow_nil - Skip validation if attribute is +nil+ (default is
# +false+). Notice that for Integer and Float columns empty strings are
# converted to +nil+.
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 8fd6e706b0..37c34d6543 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -19,8 +19,10 @@ class NumericalityValidationTest < ActiveModel::TestCase
BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significant digits
FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
- FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
- INTEGERS = [0, 10, -10] + INTEGER_STRINGS
+ NUMERIC_FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001]
+ NUMERIC_INTEGERS = [0, 10, -10]
+ FLOATS = NUMERIC_FLOATS + FLOAT_STRINGS
+ INTEGERS = NUMERIC_INTEGERS + INTEGER_STRINGS
BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal(bd) }
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "-0xdeadbeef", "+0xdeadbeef", "0xinvalidhex", "0Xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
INFINITY = [1.0 / 0.0]
@@ -74,6 +76,20 @@ class NumericalityValidationTest < ActiveModel::TestCase
assert_valid_values(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
end
+ def test_validates_numericality_of_with_numeric_only
+ Topic.validates_numericality_of :approved, only_numeric: true
+
+ assert_invalid_values(NIL + BLANK + JUNK + FLOAT_STRINGS + INTEGER_STRINGS)
+ assert_valid_values(NUMERIC_FLOATS + NUMERIC_INTEGERS + BIGDECIMAL + INFINITY)
+ end
+
+ def test_validates_numericality_of_with_numeric_only_and_nil_allowed
+ Topic.validates_numericality_of :approved, only_numeric: true, allow_nil: true
+
+ assert_invalid_values(JUNK + BLANK + FLOAT_STRINGS + INTEGER_STRINGS)
+ assert_valid_values(NIL + NUMERIC_FLOATS + NUMERIC_INTEGERS + BIGDECIMAL + INFINITY)
+ end
+
def test_validates_numericality_with_greater_than
Topic.validates_numericality_of :approved, greater_than: 10