Merge pull request #38212 from gmcgibbon/numericality_validation_with_scale

Add scale support to ActiveRecord::Validations::NumericalityValidator
This commit is contained in:
Gannon McGibbon 2020-01-13 12:32:32 -05:00 committed by GitHub
commit 8c71793b3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 15 deletions

View File

@ -22,7 +22,7 @@ module ActiveModel
end
end
def validate_each(record, attr_name, value, precision: Float::DIG)
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)
@ -43,7 +43,7 @@ module ActiveModel
raw_value = value
end
unless is_number?(raw_value, precision)
unless is_number?(raw_value, precision, scale)
record.errors.add(attr_name, :not_a_number, **filtered_options(raw_value))
return
end
@ -53,7 +53,7 @@ module ActiveModel
return
end
value = parse_as_number(raw_value, precision)
value = parse_as_number(raw_value, precision, scale)
options.slice(*CHECKS.keys).each do |option, option_value|
case option
@ -69,7 +69,7 @@ module ActiveModel
option_value = record.send(option_value)
end
option_value = parse_as_number(option_value, precision)
option_value = parse_as_number(option_value, precision, scale)
unless value.send(CHECKS[option], option_value)
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
@ -79,20 +79,24 @@ module ActiveModel
end
private
def parse_as_number(raw_value, precision)
def parse_as_number(raw_value, precision, scale)
if raw_value.is_a?(Float)
raw_value.to_d(precision)
parse_float(raw_value, precision, scale)
elsif raw_value.is_a?(Numeric)
raw_value
elsif is_integer?(raw_value)
raw_value.to_i
elsif !is_hexadecimal_literal?(raw_value)
Kernel.Float(raw_value).to_d(precision)
parse_float(Kernel.Float(raw_value), precision, scale)
end
end
def is_number?(raw_value, precision)
!parse_as_number(raw_value, precision).nil?
def parse_float(raw_value, precision, scale)
(scale ? raw_value.truncate(scale) : raw_value).to_d(precision)
end
def is_number?(raw_value, precision, scale)
!parse_as_number(raw_value, precision, scale).nil?
rescue ArgumentError, TypeError
false
end

View File

@ -1,3 +1,7 @@
* Add scale support to `ActiveRecord::Validations::NumericalityValidator`.
*Gannon McGibbon*
* Find orphans by looking for missing relations through chaining `where.missing`:
Before:

View File

@ -3,15 +3,20 @@
module ActiveRecord
module Validations
class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
def validate_each(record, attribute, value, precision: nil)
precision = [column_precision_for(attribute, record) || BigDecimal.double_fig, BigDecimal.double_fig].min
super
def validate_each(record, attribute, value, precision: nil, scale: nil)
precision = [column_precision_for(record, attribute) || BigDecimal.double_fig, BigDecimal.double_fig].min
scale = column_scale_for(record, attribute)
super(record, attribute, value, precision: precision, scale: scale)
end
private
def column_precision_for(attribute, record)
def column_precision_for(record, attribute)
record.class.type_for_attribute(attribute.to_s)&.precision
end
def column_scale_for(record, attribute)
record.class.type_for_attribute(attribute.to_s)&.scale
end
end
module ClassMethods

View File

@ -12,10 +12,10 @@ class NumericalityValidationTest < ActiveRecord::TestCase
def test_column_with_precision
model_class.validates_numericality_of(
:bank_balance, equal_to: 10_000_000.12
:unscaled_bank_balance, equal_to: 10_000_000.12
)
subject = model_class.new(bank_balance: 10_000_000.121)
subject = model_class.new(unscaled_bank_balance: 10_000_000.121)
assert_predicate subject, :valid?
end
@ -30,6 +30,16 @@ class NumericalityValidationTest < ActiveRecord::TestCase
assert_predicate subject, :valid?
end
def test_column_with_scale
model_class.validates_numericality_of(
:bank_balance, greater_than: 10
)
subject = model_class.new(bank_balance: 10.001)
assert_not_predicate subject, :valid?
end
def test_no_column_precision
model_class.validates_numericality_of(
:decimal_number, equal_to: 1_000_000_000.123454
@ -70,4 +80,26 @@ class NumericalityValidationTest < ActiveRecord::TestCase
assert_predicate(subject, :valid?)
end
def test_virtual_attribute_with_precision
model_class.attribute(:virtual_decimal_number, :decimal, precision: 5)
model_class.validates_numericality_of(
:virtual_decimal_number, equal_to: 123.45
)
subject = model_class.new(virtual_decimal_number: 123.455)
assert_predicate subject, :valid?
end
def test_virtual_attribute_with_scale
model_class.attribute(:virtual_decimal_number, :decimal, scale: 2)
model_class.validates_numericality_of(
:virtual_decimal_number, greater_than: 1
)
subject = model_class.new(virtual_decimal_number: 1.001)
assert_not_predicate subject, :valid?
end
end

View File

@ -580,6 +580,7 @@ ActiveRecord::Schema.define do
create_table :numeric_data, force: true do |t|
t.decimal :bank_balance, precision: 10, scale: 2
t.decimal :big_bank_balance, precision: 15, scale: 2
t.decimal :unscaled_bank_balance, precision: 10
t.decimal :world_population, precision: 20, scale: 0
t.decimal :my_house_population, precision: 2, scale: 0
t.decimal :decimal_number