From 47a6d788ddbab08b2a04c72cd80352aac44090ab Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 19 Aug 2018 14:57:05 +0900 Subject: [PATCH] Fix numericality validator to still use value before type cast except Active Record The purpose of fe9547b is to work type casting to value from database. But that was caused not to use the value before type cast even except Active Record. There we never guarantees that the value before type cast was going to the used in this validation, but we should not change the behavior unless there is some particular reason. To restore original behavior, still use the value before type cast if `came_from_user?` is undefined (i.e. except Active Record). Fixes #33651. Fixes #33686. --- activemodel/CHANGELOG.md | 6 ++++++ .../lib/active_model/validations/numericality.rb | 15 +++++++++++---- .../validations/numericality_validation_test.rb | 10 ++++++++++ activemodel/test/models/topic.rb | 14 ++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 4bf96e11b045e..0b2fa37787629 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,9 @@ +* Fix numericality validator to still use value before type cast except Active Record. + + Fixes #33651, #33686. + + *Ryuta Kamizono* + * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps. Before: diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 375304031613c..126a87ac6e151 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -21,10 +21,17 @@ def check_validity! def validate_each(record, attr_name, value) came_from_user = :"#{attr_name}_came_from_user?" - if record.respond_to?(came_from_user) && record.public_send(came_from_user) - raw_value = record.read_attribute_before_type_cast(attr_name) - elsif record.respond_to?(:read_attribute) - raw_value = record.read_attribute(attr_name) + if record.respond_to?(came_from_user) + if record.public_send(came_from_user) + raw_value = record.read_attribute_before_type_cast(attr_name) + elsif record.respond_to?(:read_attribute) + raw_value = record.read_attribute(attr_name) + end + else + before_type_cast = :"#{attr_name}_before_type_cast" + if record.respond_to?(before_type_cast) + raw_value = record.public_send(before_type_cast) + end end raw_value ||= value diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 01b78ae72ecf7..ca3c3bc40d772 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -262,6 +262,16 @@ def test_validates_numericality_of_for_ruby_class Person.clear_validators! end + def test_validates_numericality_using_value_before_type_cast_if_possible + Topic.validates_numericality_of :price + + topic = Topic.new(price: 50) + + assert_equal "$50.00", topic.price + assert_equal 50, topic.price_before_type_cast + assert_predicate topic, :valid? + end + def test_validates_numericality_with_exponent_number base = 10_000_000_000_000_000 Topic.validates_numericality_of :approved, less_than_or_equal_to: base diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb index b0af00ee4577d..db3284f83329c 100644 --- a/activemodel/test/models/topic.rb +++ b/activemodel/test/models/topic.rb @@ -3,6 +3,11 @@ class Topic include ActiveModel::Validations include ActiveModel::Validations::Callbacks + include ActiveModel::AttributeMethods + include ActiveSupport::NumberHelper + + attribute_method_suffix "_before_type_cast" + define_attribute_method :price def self._validates_default_keys super | [ :message ] @@ -10,6 +15,7 @@ def self._validates_default_keys attr_accessor :title, :author_name, :content, :approved, :created_at attr_accessor :after_validation_performed + attr_writer :price after_validation :perform_after_validation @@ -38,4 +44,12 @@ def my_validation def my_validation_with_arg(attr) errors.add attr, "is missing" unless send(attr) end + + def price + number_to_currency @price + end + + def attribute_before_type_cast(attr) + instance_variable_get(:"@#{attr}") + end end