From d126c0d6c0e8c0803a8ce79d2baece4d3d24e509 Mon Sep 17 00:00:00 2001 From: Gannon McGibbon Date: Sat, 8 Jul 2017 01:31:22 +0300 Subject: Fix numericality equality validation on floats --- activemodel/CHANGELOG.md | 5 ++++ .../lib/active_model/validations/numericality.rb | 32 +++++++++++++++------- .../validations/numericality_validation_test.rb | 7 +++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 0bc8728e36..38c4d0c3c7 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -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* diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index b9ae42fd39..1cafb15ac7 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -9,6 +9,9 @@ module ActiveModel 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 @@ module ActiveModel 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 @@ module ActiveModel 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 @@ module ActiveModel 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) diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index ca3c3bc40d..ca22e38c2d 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -289,6 +289,13 @@ class NumericalityValidationTest < ActiveModel::TestCase 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) -- cgit v1.2.3