1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Ensure numericality validations work with mutation

The detection of in-place changes caused a weird unexpected issue with
numericality validations. That validator (out of necessity) works on the
`_before_type_cast` version of the attribute, since on an `:integer`
type column, a non-numeric string would type cast to 0.

However, strings are mutable, and we changed strings to ensure that the
post type cast version of the attribute was a different instance than
the before type cast version (so the mutation detection can work
properly).

Even though strings are the only mutable type for which a numericality
validation makes sense, special casing strings would feel like a strange
change to make here. Instead, we can make the assumption that for all
mutable types, we should work on the post-type-cast version of the
attribute, since all cases which would return 0 for non-numeric strings
are immutable.

Fixes #17852
This commit is contained in:
Sean Griffin 2014-11-30 18:24:53 -07:00
parent 8226bad710
commit 704c658531
3 changed files with 30 additions and 6 deletions

View file

@ -23,6 +23,10 @@ module ActiveModel
raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
raw_value ||= value
if record_attribute_changed_in_place?(record, attr_name)
raw_value = value
end
return if options[:allow_nil] && raw_value.nil?
unless value = parse_raw_value_as_a_number(raw_value)
@ -86,6 +90,13 @@ module ActiveModel
options[:only_integer]
end
end
private
def record_attribute_changed_in_place?(record, attr_name)
record.respond_to?(:attribute_changed_in_place?) &&
record.attribute_changed_in_place?(attr_name.to_s)
end
end
module HelperMethods

View file

@ -69,6 +69,11 @@ module ActiveRecord
end
end
def attribute_changed_in_place?(attr_name)
old_value = original_raw_attribute(attr_name)
@attributes[attr_name].changed_in_place_from?(old_value)
end
private
def calculate_changes_from_defaults
@ -141,15 +146,10 @@ module ActiveRecord
def changed_in_place
self.class.attribute_names.select do |attr_name|
changed_in_place?(attr_name)
attribute_changed_in_place?(attr_name)
end
end
def changed_in_place?(attr_name)
old_value = original_raw_attribute(attr_name)
@attributes[attr_name].changed_in_place_from?(old_value)
end
def original_raw_attribute(attr_name)
original_raw_attributes.fetch(attr_name) do
read_attribute_before_type_cast(attr_name)

View file

@ -149,4 +149,17 @@ class ValidationsTest < ActiveRecord::TestCase
assert_equal 1, Company.validators_on(:name).size
end
def test_numericality_validation_with_mutation
Topic.class_eval do
attribute :wibble, ActiveRecord::Type::String.new
validates_numericality_of :wibble, only_integer: true
end
topic = Topic.new(wibble: '123-4567')
topic.wibble.gsub!('-', '')
assert topic.valid?
ensure
Topic.reset_column_information
end
end