Merge pull request #32852 from gmcgibbon/fix_numericality_float_equality

Fix numericality equality validation on floats
This commit is contained in:
Rafael França 2018-12-12 15:01:01 -05:00 committed by GitHub
commit 3a3a3d607e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 10 deletions

@ -1,3 +1,8 @@
* Fix numericality equality validation of `BigDecimal` and `Float`
by casting to `BigDecimal` on both ends of the validation.
*Gannon McGibbon*
* Add `#slice!` method to `ActiveModel::Errors`.
*Daniel López Prat*

@ -9,6 +9,9 @@ class NumericalityValidator < EachValidator # :nodoc:
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
INTEGER_REGEX = /\A[+-]?\d+\z/
DECIMAL_REGEX = /\A[+-]?\d+\.?\d*(e|e[+-])?\d+\z/
def check_validity!
keys = CHECKS.keys - [:odd, :even]
options.slice(*keys).each do |option, value|
@ -49,11 +52,7 @@ def validate_each(record, attr_name, value)
return
end
if raw_value.is_a?(Numeric)
value = raw_value
else
value = parse_raw_value_as_a_number(raw_value)
end
value = parse_as_number(raw_value)
options.slice(*CHECKS.keys).each do |option, option_value|
case option
@ -69,6 +68,8 @@ def validate_each(record, attr_name, value)
option_value = record.send(option_value)
end
option_value = parse_as_number(option_value)
unless value.send(CHECKS[option], option_value)
record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value))
end
@ -79,18 +80,29 @@ def validate_each(record, attr_name, value)
private
def is_number?(raw_value)
!parse_raw_value_as_a_number(raw_value).nil?
!parse_as_number(raw_value).nil?
rescue ArgumentError, TypeError
false
end
def parse_raw_value_as_a_number(raw_value)
return raw_value.to_i if is_integer?(raw_value)
Kernel.Float(raw_value) unless is_hexadecimal_literal?(raw_value)
def parse_as_number(raw_value)
if raw_value.is_a?(Float)
raw_value.to_d
elsif raw_value.is_a?(Numeric)
raw_value
elsif is_integer?(raw_value)
raw_value.to_i
elsif is_decimal?(raw_value) && !is_hexadecimal_literal?(raw_value)
BigDecimal(raw_value)
end
end
def is_integer?(raw_value)
/\A[+-]?\d+\z/ === raw_value.to_s
INTEGER_REGEX === raw_value.to_s
end
def is_decimal?(raw_value)
DECIMAL_REGEX === raw_value.to_s
end
def is_hexadecimal_literal?(raw_value)

@ -289,6 +289,13 @@ def test_validates_numericality_with_invalid_args
assert_raise(ArgumentError) { Topic.validates_numericality_of :approved, equal_to: "foo" }
end
def test_validates_numericality_equality_for_float_and_big_decimal
Topic.validates_numericality_of :approved, equal_to: BigDecimal("65.6")
invalid!([Float("65.5"), BigDecimal("65.7")], "must be equal to 65.6")
valid!([Float("65.6"), BigDecimal("65.6")])
end
private
def invalid!(values, error = nil)