diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index ea6ef0f16a..84891e57fe 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -25,37 +25,17 @@ module ActiveModel end def validate_each(record, attr_name, value, precision: Float::DIG, scale: nil) - came_from_user = :"#{attr_name}_came_from_user?" - - 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 - - if record_attribute_changed_in_place?(record, attr_name) - raw_value = value - end - - unless is_number?(raw_value, precision, scale) - record.errors.add(attr_name, :not_a_number, **filtered_options(raw_value)) + unless is_number?(value, precision, scale) + record.errors.add(attr_name, :not_a_number, **filtered_options(value)) return end - if allow_only_integer?(record) && !is_integer?(raw_value) - record.errors.add(attr_name, :not_an_integer, **filtered_options(raw_value)) + if allow_only_integer?(record) && !is_integer?(value) + record.errors.add(attr_name, :not_an_integer, **filtered_options(value)) return end - value = parse_as_number(raw_value, precision, scale) + value = parse_as_number(value, precision, scale) options.slice(*CHECKS.keys).each do |option, option_value| case option @@ -128,6 +108,27 @@ module ActiveModel end end + def read_attribute_for_validation(record, attr_name) + return super if record_attribute_changed_in_place?(record, attr_name) + + came_from_user = :"#{attr_name}_came_from_user?" + + 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 || super + end + 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) diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index c9214ac3a2..4a77c7fe5f 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -147,7 +147,7 @@ module ActiveModel # override +validate_each+ with validation logic. def validate(record) attributes.each do |attribute| - value = record.read_attribute_for_validation(attribute) + value = read_attribute_for_validation(record, attribute) next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) validate_each(record, attribute, value) end @@ -164,6 +164,11 @@ module ActiveModel # +ArgumentError+ when invalid options are supplied. def check_validity! end + + private + def read_attribute_for_validation(record, attr_name) + record.read_attribute_for_validation(attr_name) + end end # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization