mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #36664 from gmcgibbon/add_ar_numericality_validation
Add ActiveRecord::Validations::NumericalityValidator
This commit is contained in:
commit
f337669b0b
7 changed files with 103 additions and 15 deletions
|
@ -22,7 +22,7 @@ module ActiveModel
|
|||
end
|
||||
end
|
||||
|
||||
def validate_each(record, attr_name, value)
|
||||
def validate_each(record, attr_name, value, precision: Float::DIG)
|
||||
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)
|
||||
unless is_number?(raw_value, precision)
|
||||
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)
|
||||
value = parse_as_number(raw_value, precision)
|
||||
|
||||
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)
|
||||
option_value = parse_as_number(option_value, precision)
|
||||
|
||||
unless value.send(CHECKS[option], option_value)
|
||||
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
||||
|
@ -79,24 +79,24 @@ module ActiveModel
|
|||
end
|
||||
|
||||
private
|
||||
def is_number?(raw_value)
|
||||
!parse_as_number(raw_value).nil?
|
||||
rescue ArgumentError, TypeError
|
||||
false
|
||||
end
|
||||
|
||||
def parse_as_number(raw_value)
|
||||
def parse_as_number(raw_value, precision)
|
||||
if raw_value.is_a?(Float)
|
||||
raw_value.to_d
|
||||
raw_value.to_d(precision)
|
||||
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
|
||||
Kernel.Float(raw_value).to_d(precision)
|
||||
end
|
||||
end
|
||||
|
||||
def is_number?(raw_value, precision)
|
||||
!parse_as_number(raw_value, precision).nil?
|
||||
rescue ArgumentError, TypeError
|
||||
false
|
||||
end
|
||||
|
||||
def is_integer?(raw_value)
|
||||
INTEGER_REGEX.match?(raw_value.to_s)
|
||||
end
|
||||
|
@ -132,7 +132,8 @@ module ActiveModel
|
|||
# Validates whether the value of the specified attribute is numeric by
|
||||
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
|
||||
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
|
||||
# (if <tt>only_integer</tt> is set to +true+).
|
||||
# (if <tt>only_integer</tt> is set to +true+). Precision of Kernel.Float values
|
||||
# are guaranteed up to 15 digits.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_numericality_of :value, on: :create
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
* Add `ActiveRecord::Validations::NumericalityValidator` with
|
||||
support for casting floats using a database columns' precision value.
|
||||
|
||||
*Gannon McGibbon*
|
||||
|
||||
* Enforce fresh ETag header after a collection's contents change by adding
|
||||
ActiveRecord::Relation#cache_key_with_version. This method will be used by
|
||||
ActionController::ConditionalGet to ensure that when collection cache versioning
|
||||
|
|
|
@ -91,3 +91,4 @@ require "active_record/validations/uniqueness"
|
|||
require "active_record/validations/presence"
|
||||
require "active_record/validations/absence"
|
||||
require "active_record/validations/length"
|
||||
require "active_record/validations/numericality"
|
||||
|
|
37
activerecord/lib/active_record/validations/numericality.rb
Normal file
37
activerecord/lib/active_record/validations/numericality.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveRecord
|
||||
module Validations
|
||||
class NumericalityValidator < ActiveModel::Validations::NumericalityValidator # :nodoc:
|
||||
def initialize(options)
|
||||
super
|
||||
@klass = options[:class]
|
||||
end
|
||||
|
||||
def validate_each(record, attribute, value, precision: nil)
|
||||
precision = column_precision_for(attribute) || Float::DIG
|
||||
super
|
||||
end
|
||||
|
||||
private
|
||||
def column_precision_for(attribute)
|
||||
if @klass < ActiveRecord::Base
|
||||
@klass.type_for_attribute(attribute.to_s)&.precision
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Validates whether the value of the specified attribute is numeric by
|
||||
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
|
||||
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
|
||||
# (if <tt>only_integer</tt> is set to +true+). Kernel.Float precision
|
||||
# defaults to the column's precision value or 15.
|
||||
#
|
||||
# See ActiveModel::Validations::HelperMethods.validates_numericality_of for more information.
|
||||
def validates_numericality_of(*attr_names)
|
||||
validates_with NumericalityValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
require "models/numeric_data"
|
||||
|
||||
class NumericalityValidationTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
@model_class = NumericData.dup
|
||||
end
|
||||
|
||||
attr_reader :model_class
|
||||
|
||||
def test_column_with_precision
|
||||
model_class.validates_numericality_of(
|
||||
:bank_balance, equal_to: 10_000_000.12
|
||||
)
|
||||
|
||||
subject = model_class.new(bank_balance: 10_000_000.121)
|
||||
|
||||
assert_predicate subject, :valid?
|
||||
end
|
||||
|
||||
def test_no_column_precision
|
||||
model_class.validates_numericality_of(
|
||||
:decimal_number, equal_to: 1_000_000_000.12345
|
||||
)
|
||||
|
||||
subject = model_class.new(decimal_number: 1_000_000_000.123454)
|
||||
|
||||
assert_predicate subject, :valid?
|
||||
end
|
||||
|
||||
def test_virtual_attribute
|
||||
model_class.attribute(:virtual_decimal_number, :decimal)
|
||||
model_class.validates_numericality_of(
|
||||
:virtual_decimal_number, equal_to: 1_000_000_000.12345
|
||||
)
|
||||
|
||||
subject = model_class.new(virtual_decimal_number: 1_000_000_000.123454)
|
||||
|
||||
assert_predicate subject, :valid?
|
||||
end
|
||||
end
|
|
@ -582,6 +582,7 @@ ActiveRecord::Schema.define do
|
|||
t.decimal :big_bank_balance, precision: 15, scale: 2
|
||||
t.decimal :world_population, precision: 20, scale: 0
|
||||
t.decimal :my_house_population, precision: 2, scale: 0
|
||||
t.decimal :decimal_number
|
||||
t.decimal :decimal_number_with_default, precision: 3, scale: 2, default: 2.78
|
||||
t.float :temperature
|
||||
# Oracle/SQLServer supports precision up to 38
|
||||
|
|
|
@ -487,7 +487,7 @@ If you set `:only_integer` to `true`, then it will use the
|
|||
```
|
||||
|
||||
regular expression to validate the attribute's value. Otherwise, it will try to
|
||||
convert the value to a number using `Float`.
|
||||
convert the value to a number using `Float`. `Float`s are casted to `BigDecimal` using the column's precision value or 15.
|
||||
|
||||
```ruby
|
||||
class Player < ApplicationRecord
|
||||
|
|
Loading…
Reference in a new issue