From d126c0d6c0e8c0803a8ce79d2baece4d3d24e509 Mon Sep 17 00:00:00 2001
From: Gannon McGibbon <gannon.mcgibbon@gmail.com>
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