mirror of https://github.com/rails/rails
Merge pull request #38212 from gmcgibbon/numericality_validation_with_scale
Add scale support to ActiveRecord::Validations::NumericalityValidator
This commit is contained in:
commit
8c71793b3b
|
@ -22,7 +22,7 @@ module ActiveModel
|
||||||
end
|
end
|
||||||
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?"
|
came_from_user = :"#{attr_name}_came_from_user?"
|
||||||
|
|
||||||
if record.respond_to?(came_from_user)
|
if record.respond_to?(came_from_user)
|
||||||
|
@ -43,7 +43,7 @@ module ActiveModel
|
||||||
raw_value = value
|
raw_value = value
|
||||||
end
|
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))
|
record.errors.add(attr_name, :not_a_number, **filtered_options(raw_value))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -53,7 +53,7 @@ module ActiveModel
|
||||||
return
|
return
|
||||||
end
|
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|
|
options.slice(*CHECKS.keys).each do |option, option_value|
|
||||||
case option
|
case option
|
||||||
|
@ -69,7 +69,7 @@ module ActiveModel
|
||||||
option_value = record.send(option_value)
|
option_value = record.send(option_value)
|
||||||
end
|
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)
|
unless value.send(CHECKS[option], option_value)
|
||||||
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
||||||
|
@ -79,20 +79,24 @@ module ActiveModel
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def parse_as_number(raw_value, precision)
|
def parse_as_number(raw_value, precision, scale)
|
||||||
if raw_value.is_a?(Float)
|
if raw_value.is_a?(Float)
|
||||||
raw_value.to_d(precision)
|
parse_float(raw_value, precision, scale)
|
||||||
elsif raw_value.is_a?(Numeric)
|
elsif raw_value.is_a?(Numeric)
|
||||||
raw_value
|
raw_value
|
||||||
elsif is_integer?(raw_value)
|
elsif is_integer?(raw_value)
|
||||||
raw_value.to_i
|
raw_value.to_i
|
||||||
elsif !is_hexadecimal_literal?(raw_value)
|
elsif !is_hexadecimal_literal?(raw_value)
|
||||||
Kernel.Float(raw_value).to_d(precision)
|
parse_float(Kernel.Float(raw_value), precision, scale)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_number?(raw_value, precision)
|
def parse_float(raw_value, precision, scale)
|
||||||
!parse_as_number(raw_value, precision).nil?
|
(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
|
rescue ArgumentError, TypeError
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
* Add scale support to `ActiveRecord::Validations::NumericalityValidator`.
|
||||||
|
|
||||||
|
*Gannon McGibbon*
|
||||||
|
|
||||||
* Find orphans by looking for missing relations through chaining `where.missing`:
|
* Find orphans by looking for missing relations through chaining `where.missing`:
|
||||||
|
|
||||||
Before:
|
Before:
|
||||||
|
|
|
@ -3,15 +3,20 @@
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
module Validations
|
module Validations
|
||||||
class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
|
class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
|
||||||
def validate_each(record, attribute, value, precision: nil)
|
def validate_each(record, attribute, value, precision: nil, scale: nil)
|
||||||
precision = [column_precision_for(attribute, record) || BigDecimal.double_fig, BigDecimal.double_fig].min
|
precision = [column_precision_for(record, attribute) || BigDecimal.double_fig, BigDecimal.double_fig].min
|
||||||
super
|
scale = column_scale_for(record, attribute)
|
||||||
|
super(record, attribute, value, precision: precision, scale: scale)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def column_precision_for(attribute, record)
|
def column_precision_for(record, attribute)
|
||||||
record.class.type_for_attribute(attribute.to_s)&.precision
|
record.class.type_for_attribute(attribute.to_s)&.precision
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def column_scale_for(record, attribute)
|
||||||
|
record.class.type_for_attribute(attribute.to_s)&.scale
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
|
@ -12,10 +12,10 @@ class NumericalityValidationTest < ActiveRecord::TestCase
|
||||||
|
|
||||||
def test_column_with_precision
|
def test_column_with_precision
|
||||||
model_class.validates_numericality_of(
|
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?
|
assert_predicate subject, :valid?
|
||||||
end
|
end
|
||||||
|
@ -30,6 +30,16 @@ class NumericalityValidationTest < ActiveRecord::TestCase
|
||||||
assert_predicate subject, :valid?
|
assert_predicate subject, :valid?
|
||||||
end
|
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
|
def test_no_column_precision
|
||||||
model_class.validates_numericality_of(
|
model_class.validates_numericality_of(
|
||||||
:decimal_number, equal_to: 1_000_000_000.123454
|
:decimal_number, equal_to: 1_000_000_000.123454
|
||||||
|
@ -70,4 +80,26 @@ class NumericalityValidationTest < ActiveRecord::TestCase
|
||||||
|
|
||||||
assert_predicate(subject, :valid?)
|
assert_predicate(subject, :valid?)
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -580,6 +580,7 @@ ActiveRecord::Schema.define do
|
||||||
create_table :numeric_data, force: true do |t|
|
create_table :numeric_data, force: true do |t|
|
||||||
t.decimal :bank_balance, precision: 10, scale: 2
|
t.decimal :bank_balance, precision: 10, scale: 2
|
||||||
t.decimal :big_bank_balance, precision: 15, 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 :world_population, precision: 20, scale: 0
|
||||||
t.decimal :my_house_population, precision: 2, scale: 0
|
t.decimal :my_house_population, precision: 2, scale: 0
|
||||||
t.decimal :decimal_number
|
t.decimal :decimal_number
|
||||||
|
|
Loading…
Reference in New Issue