mirror of https://github.com/rails/rails
Merge pull request #40095 from ChaelCodes/cc-comparablity-validator
Add ComparisonValidator to validate comparison of any objects
This commit is contained in:
commit
536a2c0011
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveModel
|
||||
module Validations
|
||||
module Comparability #:nodoc:
|
||||
COMPARE_CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
|
||||
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
|
||||
other_than: :!= }.freeze
|
||||
|
||||
def option_value(record, option_value)
|
||||
case option_value
|
||||
when Proc
|
||||
option_value.call(record)
|
||||
when Symbol
|
||||
record.send(option_value)
|
||||
else
|
||||
option_value
|
||||
end
|
||||
end
|
||||
|
||||
def error_options(value, option_value)
|
||||
options.except(*COMPARE_CHECKS.keys).merge!(
|
||||
count: option_value,
|
||||
value: value
|
||||
)
|
||||
end
|
||||
|
||||
def error_value(record, option_value)
|
||||
case option_value
|
||||
when Proc
|
||||
option_value(record, option_value)
|
||||
else
|
||||
option_value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,78 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActiveModel
|
||||
module Validations
|
||||
class ComparisonValidator < EachValidator # :nodoc:
|
||||
include Comparability
|
||||
|
||||
def check_validity!
|
||||
unless (options.keys & COMPARE_CHECKS.keys).any?
|
||||
raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
|
||||
":equal_to, :less_than, :less_than_or_equal_to, nor :other_than supplied."
|
||||
end
|
||||
end
|
||||
|
||||
def validate_each(record, attr_name, value)
|
||||
options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value|
|
||||
if value.nil? || value.blank?
|
||||
return record.errors.add(attr_name, :blank, **error_options(value, error_value(record, raw_option_value)))
|
||||
end
|
||||
|
||||
unless value.send(COMPARE_CHECKS[option], option_value(record, raw_option_value))
|
||||
record.errors.add(attr_name, option, **error_options(value, error_value(record, raw_option_value)))
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
record.errors.add(attr_name, e.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module HelperMethods
|
||||
# Validates the value of a specified attribute fulfills all
|
||||
# defined comparisons with another value, proc, or attribute.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_comparison_of :value, greater_than: 'the sum of its parts'
|
||||
# end
|
||||
#
|
||||
# Configuration options:
|
||||
# * <tt>:message</tt> - A custom error message (default is: "failed comparison").
|
||||
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
|
||||
# supplied value.
|
||||
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
|
||||
# greater than or equal the supplied value.
|
||||
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
|
||||
# value.
|
||||
# * <tt>:less_than</tt> - Specifies the value must be less than the
|
||||
# supplied value.
|
||||
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
|
||||
# than or equal the supplied value.
|
||||
# * <tt>:other_than</tt> - Specifies the value must not be equal to the
|
||||
# supplied value.
|
||||
#
|
||||
# There is also a list of default options supported by every validator:
|
||||
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
||||
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
||||
#
|
||||
# The validator requires at least one of the following checks be supplied.
|
||||
# Each will accept a proc, value, or a symbol which corresponds to a method:
|
||||
#
|
||||
# * <tt>:greater_than</tt>
|
||||
# * <tt>:greater_than_or_equal_to</tt>
|
||||
# * <tt>:equal_to</tt>
|
||||
# * <tt>:less_than</tt>
|
||||
# * <tt>:less_than_or_equal_to</tt>
|
||||
# * <tt>:other_than</tt>
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
|
||||
# validates_comparison_of :preferred_name, other_than: :given_name, allow_nil: true
|
||||
# end
|
||||
def validates_comparison_of(*attr_names)
|
||||
validates_with ComparisonValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,24 +5,25 @@ require "bigdecimal/util"
|
|||
module ActiveModel
|
||||
module Validations
|
||||
class NumericalityValidator < EachValidator # :nodoc:
|
||||
CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
|
||||
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
|
||||
odd: :odd?, even: :even?, other_than: :!=, in: :in? }.freeze
|
||||
include Comparability
|
||||
|
||||
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
|
||||
RANGE_CHECKS = { in: :in? }
|
||||
NUMBER_CHECKS = { odd: :odd?, even: :even? }
|
||||
|
||||
RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer]
|
||||
|
||||
INTEGER_REGEX = /\A[+-]?\d+\z/
|
||||
|
||||
HEXADECIMAL_REGEX = /\A[+-]?0[xX]/
|
||||
|
||||
def check_validity!
|
||||
keys = CHECKS.keys - [:odd, :even, :in]
|
||||
options.slice(*keys).each do |option, value|
|
||||
options.slice(*COMPARE_CHECKS.keys).each do |option, value|
|
||||
unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
|
||||
raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
|
||||
end
|
||||
end
|
||||
options.slice(:in).each do |option, value|
|
||||
|
||||
options.slice(*RANGE_CHECKS).each do |option, value|
|
||||
unless value.is_a?(Range)
|
||||
raise ArgumentError, ":#{option} must be a range"
|
||||
end
|
||||
|
@ -42,23 +43,18 @@ module ActiveModel
|
|||
|
||||
value = parse_as_number(value, precision, scale)
|
||||
|
||||
options.slice(*CHECKS.keys).each do |option, option_value|
|
||||
case option
|
||||
when :odd, :even
|
||||
unless value.to_i.public_send(CHECKS[option])
|
||||
options.slice(*RESERVED_OPTIONS).each do |option, option_value|
|
||||
if NUMBER_CHECKS.keys.include? option
|
||||
unless value.to_i.send(NUMBER_CHECKS[option])
|
||||
record.errors.add(attr_name, option, **filtered_options(value))
|
||||
end
|
||||
else
|
||||
case option_value
|
||||
when Proc
|
||||
option_value = option_value.call(record)
|
||||
when Symbol
|
||||
option_value = record.send(option_value)
|
||||
elsif RANGE_CHECKS.keys.include? option
|
||||
unless value.send(RANGE_CHECKS[option], option_value)
|
||||
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
||||
end
|
||||
|
||||
option_value = parse_as_number(option_value, precision, scale, option)
|
||||
|
||||
unless value.public_send(CHECKS[option], option_value)
|
||||
elsif COMPARE_CHECKS.keys.include? option
|
||||
option_value = option_as_number(record, option_value, precision, scale)
|
||||
unless value.send(COMPARE_CHECKS[option], option_value)
|
||||
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
||||
end
|
||||
end
|
||||
|
@ -66,10 +62,12 @@ module ActiveModel
|
|||
end
|
||||
|
||||
private
|
||||
def parse_as_number(raw_value, precision, scale, option = nil)
|
||||
if option == :in
|
||||
raw_value if raw_value.is_a?(Range)
|
||||
elsif raw_value.is_a?(Float)
|
||||
def option_as_number(record, option_value, precision, scale)
|
||||
parse_as_number(option_value(record, option_value), precision, scale)
|
||||
end
|
||||
|
||||
def parse_as_number(raw_value, precision, scale)
|
||||
if raw_value.is_a?(Float)
|
||||
parse_float(raw_value, precision, scale)
|
||||
elsif raw_value.is_a?(BigDecimal)
|
||||
round(raw_value, scale)
|
||||
|
@ -180,6 +178,7 @@ module ActiveModel
|
|||
# supplied value.
|
||||
# * <tt>:odd</tt> - Specifies the value must be an odd number.
|
||||
# * <tt>:even</tt> - Specifies the value must be an even number.
|
||||
# * <tt>:in</tt> - Check that the value is within a range.
|
||||
#
|
||||
# There is also a list of default options supported by every validator:
|
||||
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
|
||||
require "models/topic"
|
||||
require "models/person"
|
||||
|
||||
class ComparisonValidationTest < ActiveModel::TestCase
|
||||
def teardown
|
||||
Topic.clear_validators!
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_greater_than_using_numeric
|
||||
Topic.validates_comparison_of :approved, greater_than: 10
|
||||
|
||||
assert_invalid_values([-12, 10], "must be greater than 10")
|
||||
assert_valid_values([11])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_greater_than_using_date
|
||||
date_value = Date.parse("2020-08-02")
|
||||
Topic.validates_comparison_of :approved, greater_than: date_value
|
||||
|
||||
assert_invalid_values([
|
||||
Date.parse("2019-08-03"),
|
||||
Date.parse("2020-07-03"),
|
||||
Date.parse("2020-08-01"),
|
||||
Date.parse("2020-08-02"),
|
||||
DateTime.new(2020, 8, 1, 12, 34)], "must be greater than 2020-08-02")
|
||||
assert_valid_values([Date.parse("2020-08-03"), DateTime.new(2020, 8, 2, 12, 34)])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_greater_than_using_string
|
||||
Topic.validates_comparison_of :approved, greater_than: "cat"
|
||||
|
||||
assert_invalid_values(["ant", "cat"], "must be greater than cat")
|
||||
assert_valid_values(["dog", "whale"])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_greater_than_or_equal_to_using_numeric
|
||||
Topic.validates_comparison_of :approved, greater_than_or_equal_to: 10
|
||||
|
||||
assert_invalid_values([-12, 5], "must be greater than or equal to 10")
|
||||
assert_valid_values([11, 10])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_greater_than_or_equal_to_using_string
|
||||
Topic.validates_comparison_of :approved, greater_than_or_equal_to: "cat"
|
||||
|
||||
assert_invalid_values(["ant"], "must be greater than or equal to cat")
|
||||
assert_valid_values(["cat", "dog", "whale"])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_greater_than_or_equal_to_using_date
|
||||
date_value = Date.parse("2020-08-02")
|
||||
Topic.validates_comparison_of :approved, greater_than_or_equal_to: date_value
|
||||
|
||||
assert_invalid_values([
|
||||
Date.parse("2019-08-03"),
|
||||
Date.parse("2020-07-03"),
|
||||
Date.parse("2020-08-01"),
|
||||
DateTime.new(2020, 8, 1, 12, 34)], "must be greater than or equal to 2020-08-02")
|
||||
assert_valid_values([Date.parse("2020-08-03"), DateTime.new(2020, 8, 2, 12, 34), Date.parse("2020-08-02")])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_equal_to_using_numeric
|
||||
Topic.validates_comparison_of :approved, equal_to: 10
|
||||
|
||||
assert_invalid_values([-12, 5, 11], "must be equal to 10")
|
||||
assert_valid_values([10])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_equal_to_using_date
|
||||
date_value = Date.parse("2020-08-02")
|
||||
Topic.validates_comparison_of :approved, equal_to: date_value
|
||||
|
||||
assert_invalid_values([
|
||||
Date.parse("2019-08-03"),
|
||||
Date.parse("2020-07-03"),
|
||||
Date.parse("2020-08-01"),
|
||||
DateTime.new(2020, 8, 1, 12, 34),
|
||||
Date.parse("2020-08-03"),
|
||||
DateTime.new(2020, 8, 2, 12, 34)], "must be equal to 2020-08-02")
|
||||
assert_valid_values([Date.parse("2020-08-02"), DateTime.new(2020, 8, 2, 0, 0)])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_less_than_using_numeric
|
||||
Topic.validates_comparison_of :approved, less_than: 10
|
||||
|
||||
assert_invalid_values([11, 10], "must be less than 10")
|
||||
assert_valid_values([-12, -5, 5])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_less_than_using_date
|
||||
date_value = Date.parse("2020-08-02")
|
||||
Topic.validates_comparison_of :approved, less_than: date_value
|
||||
|
||||
assert_invalid_values([
|
||||
Date.parse("2020-08-02"),
|
||||
Date.parse("2020-08-03"),
|
||||
DateTime.new(2020, 8, 2, 12, 34)], "must be less than 2020-08-02")
|
||||
assert_valid_values([Date.parse("2019-08-03"),
|
||||
Date.parse("2020-07-03"),
|
||||
Date.parse("2020-08-01"),
|
||||
DateTime.new(2020, 8, 1, 12, 34)])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_less_than_or_equal_to_using_numeric
|
||||
Topic.validates_comparison_of :approved, less_than_or_equal_to: 10
|
||||
|
||||
assert_invalid_values([12], "must be less than or equal to 10")
|
||||
assert_valid_values([-11, 5, 10])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_less_than_or_equal_to_using_date
|
||||
date_value = Date.parse("2020-08-02")
|
||||
Topic.validates_comparison_of :approved, less_than_or_equal_to: date_value
|
||||
|
||||
assert_invalid_values([
|
||||
Date.parse("2020-08-03"),
|
||||
DateTime.new(2020, 8, 2, 12, 34)], "must be less than or equal to 2020-08-02")
|
||||
assert_valid_values([Date.parse("2019-08-03"),
|
||||
Date.parse("2020-07-03"),
|
||||
Date.parse("2020-08-01"),
|
||||
Date.parse("2020-08-02"),
|
||||
DateTime.new(2020, 8, 1, 12, 34)])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_other_than_using_numeric
|
||||
Topic.validates_comparison_of :approved, other_than: 10
|
||||
|
||||
assert_invalid_values([10], "must be other than 10")
|
||||
assert_valid_values([-12, 5, 11])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_other_than_using_date
|
||||
date_value = Date.parse("2020-08-02")
|
||||
Topic.validates_comparison_of :approved, other_than: date_value
|
||||
|
||||
assert_invalid_values([Date.parse("2020-08-02"), DateTime.new(2020, 8, 2, 0, 0)], "must be other than 2020-08-02")
|
||||
assert_valid_values([
|
||||
Date.parse("2019-08-03"),
|
||||
Date.parse("2020-07-03"),
|
||||
Date.parse("2020-08-01"),
|
||||
DateTime.new(2020, 8, 1, 12, 34),
|
||||
Date.parse("2020-08-03"),
|
||||
DateTime.new(2020, 8, 2, 12, 34)])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_proc
|
||||
Topic.define_method(:requested) { Date.new(2020, 8, 1) }
|
||||
Topic.validates_comparison_of :approved, greater_than_or_equal_to: Proc.new(&:requested)
|
||||
|
||||
assert_invalid_values([Date.new(2020, 7, 1), Date.new(2019, 7, 1), DateTime.new(2020, 7, 1, 22, 34)])
|
||||
assert_valid_values([Date.new(2020, 8, 2), DateTime.new(2021, 8, 1)])
|
||||
ensure
|
||||
Topic.remove_method :requested
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_method
|
||||
Topic.define_method(:requested) { Date.new(2020, 8, 1) }
|
||||
Topic.validates_comparison_of :approved, greater_than_or_equal_to: :requested
|
||||
|
||||
assert_invalid_values([Date.new(2020, 7, 1), Date.new(2019, 7, 1), DateTime.new(2020, 7, 1, 22, 34)])
|
||||
assert_valid_values([Date.new(2020, 8, 2), DateTime.new(2021, 8, 1)])
|
||||
ensure
|
||||
Topic.remove_method :requested
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_custom_compare
|
||||
custom = Struct.new(:amount) {
|
||||
include Comparable
|
||||
|
||||
def <=>(other)
|
||||
amount % 100 <=> other.amount % 100
|
||||
end
|
||||
}
|
||||
Topic.validates_comparison_of :approved, greater_than_or_equal_to: custom.new(1150)
|
||||
|
||||
assert_invalid_values([custom.new(530), custom.new(2325)])
|
||||
assert_valid_values([custom.new(575), custom.new(250), custom.new(1999)])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_blank_allowed
|
||||
Topic.validates_comparison_of :approved, greater_than: "cat", allow_blank: true
|
||||
|
||||
assert_invalid_values(["ant"])
|
||||
assert_valid_values([nil, ""])
|
||||
end
|
||||
|
||||
def test_validates_comparison_with_nil_allowed
|
||||
Topic.validates_comparison_of :approved, less_than: 100, allow_nil: true
|
||||
|
||||
assert_invalid_values([200])
|
||||
assert_valid_values([nil, 50])
|
||||
end
|
||||
|
||||
def test_validates_comparison_of_incomparables
|
||||
Topic.validates_comparison_of :approved, less_than: "cat"
|
||||
|
||||
assert_invalid_values([12], "comparison of Integer with String failed")
|
||||
assert_invalid_values([nil])
|
||||
assert_valid_values([])
|
||||
end
|
||||
|
||||
def test_validates_comparison_of_multiple_values
|
||||
Topic.validates_comparison_of :approved, other_than: 17, greater_than: 13
|
||||
|
||||
assert_invalid_values([12, nil, 17])
|
||||
assert_valid_values([15])
|
||||
end
|
||||
|
||||
def test_validates_comparison_of_no_options
|
||||
error = assert_raises(ArgumentError) do
|
||||
Topic.validates_comparison_of(:approved)
|
||||
end
|
||||
assert_equal "Expected one of :greater_than, :greater_than_or_equal_to, :equal_to," \
|
||||
" :less_than, :less_than_or_equal_to, nor :other_than supplied.", error.message
|
||||
end
|
||||
|
||||
private
|
||||
def assert_invalid_values(values, error = nil)
|
||||
with_each_topic_approved_value(values) do |topic, value|
|
||||
assert topic.invalid?, "#{value.inspect} failed comparison"
|
||||
assert topic.errors[:approved].any?, "FAILED for #{value.inspect}"
|
||||
assert_equal error, topic.errors[:approved].first if error
|
||||
end
|
||||
end
|
||||
|
||||
def assert_valid_values(values)
|
||||
with_each_topic_approved_value(values) do |topic, value|
|
||||
assert topic.valid?, "#{value.inspect} failed comparison with validation error: #{topic.errors[:approved].first}"
|
||||
end
|
||||
end
|
||||
|
||||
def with_each_topic_approved_value(values)
|
||||
topic = Topic.new(title: "comparison test", content: "whatever")
|
||||
values.each do |value|
|
||||
topic.approved = value
|
||||
yield topic, value
|
||||
end
|
||||
end
|
||||
end
|
|
@ -27,206 +27,206 @@ class NumericalityValidationTest < ActiveModel::TestCase
|
|||
|
||||
def test_default_validates_numericality_of
|
||||
Topic.validates_numericality_of :approved
|
||||
invalid!(NIL + BLANK + JUNK)
|
||||
valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
assert_invalid_values(NIL + BLANK + JUNK)
|
||||
assert_valid_values(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_nil_allowed
|
||||
Topic.validates_numericality_of :approved, allow_nil: true
|
||||
|
||||
invalid!(JUNK + BLANK)
|
||||
valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
assert_invalid_values(JUNK + BLANK)
|
||||
assert_valid_values(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_blank_allowed
|
||||
Topic.validates_numericality_of :approved, allow_blank: true
|
||||
|
||||
invalid!(JUNK)
|
||||
valid!(NIL + BLANK + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
assert_invalid_values(JUNK)
|
||||
assert_valid_values(NIL + BLANK + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_integer_only
|
||||
Topic.validates_numericality_of :approved, only_integer: true
|
||||
|
||||
invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
|
||||
valid!(INTEGERS)
|
||||
assert_invalid_values(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
|
||||
assert_valid_values(INTEGERS)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_integer_only_and_nil_allowed
|
||||
Topic.validates_numericality_of :approved, only_integer: true, allow_nil: true
|
||||
|
||||
invalid!(JUNK + BLANK + FLOATS + BIGDECIMAL + INFINITY)
|
||||
valid!(NIL + INTEGERS)
|
||||
assert_invalid_values(JUNK + BLANK + FLOATS + BIGDECIMAL + INFINITY)
|
||||
assert_valid_values(NIL + INTEGERS)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_integer_only_and_symbol_as_value
|
||||
Topic.validates_numericality_of :approved, only_integer: :condition_is_false
|
||||
|
||||
invalid!(NIL + BLANK + JUNK)
|
||||
valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
assert_invalid_values(NIL + BLANK + JUNK)
|
||||
assert_valid_values(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_integer_only_and_proc_as_value
|
||||
Topic.define_method(:allow_only_integers?) { false }
|
||||
Topic.validates_numericality_of :approved, only_integer: Proc.new(&:allow_only_integers?)
|
||||
|
||||
invalid!(NIL + BLANK + JUNK)
|
||||
valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
assert_invalid_values(NIL + BLANK + JUNK)
|
||||
assert_valid_values(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_greater_than
|
||||
Topic.validates_numericality_of :approved, greater_than: 10
|
||||
|
||||
invalid!([-10, 10], "must be greater than 10")
|
||||
valid!([11])
|
||||
assert_invalid_values([-10, 10], "must be greater than 10")
|
||||
assert_valid_values([11])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_greater_than_using_differing_numeric_types
|
||||
Topic.validates_numericality_of :approved, greater_than: BigDecimal("97.18")
|
||||
|
||||
invalid!([-97.18, BigDecimal("97.18"), BigDecimal("-97.18")], "must be greater than 97.18")
|
||||
valid!([97.19, 98, BigDecimal("98"), BigDecimal("97.19")])
|
||||
assert_invalid_values([-97.18, BigDecimal("97.18"), BigDecimal("-97.18")], "must be greater than 97.18")
|
||||
assert_valid_values([97.19, 98, BigDecimal("98"), BigDecimal("97.19")])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_greater_than_using_string_value
|
||||
Topic.validates_numericality_of :approved, greater_than: 10
|
||||
|
||||
invalid!(["-10", "9", "9.9", "10"], "must be greater than 10")
|
||||
valid!(["10.1", "11"])
|
||||
assert_invalid_values(["-10", "9", "9.9", "10"], "must be greater than 10")
|
||||
assert_valid_values(["10.1", "11"])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_greater_than_or_equal
|
||||
Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10
|
||||
|
||||
invalid!([-9, 9], "must be greater than or equal to 10")
|
||||
valid!([10])
|
||||
assert_invalid_values([-9, 9], "must be greater than or equal to 10")
|
||||
assert_valid_values([10])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_greater_than_or_equal_using_differing_numeric_types
|
||||
Topic.validates_numericality_of :approved, greater_than_or_equal_to: BigDecimal("97.18")
|
||||
|
||||
invalid!([-97.18, 97.17, 97, BigDecimal("97.17"), BigDecimal("-97.18")], "must be greater than or equal to 97.18")
|
||||
valid!([97.18, 98, BigDecimal("97.19")])
|
||||
assert_invalid_values([-97.18, 97.17, 97, BigDecimal("97.17"), BigDecimal("-97.18")], "must be greater than or equal to 97.18")
|
||||
assert_valid_values([97.18, 98, BigDecimal("97.19")])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_greater_than_or_equal_using_string_value
|
||||
Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10
|
||||
|
||||
invalid!(["-10", "9", "9.9"], "must be greater than or equal to 10")
|
||||
valid!(["10", "10.1", "11"])
|
||||
assert_invalid_values(["-10", "9", "9.9"], "must be greater than or equal to 10")
|
||||
assert_valid_values(["10", "10.1", "11"])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_equal_to
|
||||
Topic.validates_numericality_of :approved, equal_to: 10
|
||||
|
||||
invalid!([-10, 11] + INFINITY, "must be equal to 10")
|
||||
valid!([10])
|
||||
assert_invalid_values([-10, 11] + INFINITY, "must be equal to 10")
|
||||
assert_valid_values([10])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_equal_to_using_differing_numeric_types
|
||||
Topic.validates_numericality_of :approved, equal_to: BigDecimal("97.18")
|
||||
|
||||
invalid!([-97.18], "must be equal to 97.18")
|
||||
valid!([BigDecimal("97.18")])
|
||||
assert_invalid_values([-97.18], "must be equal to 97.18")
|
||||
assert_valid_values([BigDecimal("97.18")])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_equal_to_using_string_value
|
||||
Topic.validates_numericality_of :approved, equal_to: 10
|
||||
|
||||
invalid!(["-10", "9", "9.9", "10.1", "11"], "must be equal to 10")
|
||||
valid!(["10"])
|
||||
assert_invalid_values(["-10", "9", "9.9", "10.1", "11"], "must be equal to 10")
|
||||
assert_valid_values(["10"])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_less_than
|
||||
Topic.validates_numericality_of :approved, less_than: 10
|
||||
|
||||
invalid!([10], "must be less than 10")
|
||||
valid!([-9, 9])
|
||||
assert_invalid_values([10], "must be less than 10")
|
||||
assert_valid_values([-9, 9])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_less_than_using_differing_numeric_types
|
||||
Topic.validates_numericality_of :approved, less_than: BigDecimal("97.18")
|
||||
|
||||
invalid!([97.18, BigDecimal("97.18")], "must be less than 97.18")
|
||||
valid!([-97.0, 97.0, -97, 97, BigDecimal("-97"), BigDecimal("97")])
|
||||
assert_invalid_values([97.18, BigDecimal("97.18")], "must be less than 97.18")
|
||||
assert_valid_values([-97.0, 97.0, -97, 97, BigDecimal("-97"), BigDecimal("97")])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_less_than_using_string_value
|
||||
Topic.validates_numericality_of :approved, less_than: 10
|
||||
|
||||
invalid!(["10", "10.1", "11"], "must be less than 10")
|
||||
valid!(["-10", "9", "9.9"])
|
||||
assert_invalid_values(["10", "10.1", "11"], "must be less than 10")
|
||||
assert_valid_values(["-10", "9", "9.9"])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_less_than_or_equal_to
|
||||
Topic.validates_numericality_of :approved, less_than_or_equal_to: 10
|
||||
|
||||
invalid!([11], "must be less than or equal to 10")
|
||||
valid!([-10, 10])
|
||||
assert_invalid_values([11], "must be less than or equal to 10")
|
||||
assert_valid_values([-10, 10])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_less_than_or_equal_to_using_differing_numeric_types
|
||||
Topic.validates_numericality_of :approved, less_than_or_equal_to: BigDecimal("97.18")
|
||||
|
||||
invalid!([97.19, 98], "must be less than or equal to 97.18")
|
||||
valid!([-97.18, BigDecimal("-97.18"), BigDecimal("97.18")])
|
||||
assert_invalid_values([97.19, 98], "must be less than or equal to 97.18")
|
||||
assert_valid_values([-97.18, BigDecimal("-97.18"), BigDecimal("97.18")])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_less_than_or_equal_using_string_value
|
||||
Topic.validates_numericality_of :approved, less_than_or_equal_to: 10
|
||||
|
||||
invalid!(["10.1", "11"], "must be less than or equal to 10")
|
||||
valid!(["-10", "9", "9.9", "10"])
|
||||
assert_invalid_values(["10.1", "11"], "must be less than or equal to 10")
|
||||
assert_valid_values(["-10", "9", "9.9", "10"])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_odd
|
||||
Topic.validates_numericality_of :approved, odd: true
|
||||
|
||||
invalid!([-2, 2], "must be odd")
|
||||
valid!([-1, 1])
|
||||
assert_invalid_values([-2, 2], "must be odd")
|
||||
assert_valid_values([-1, 1])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_even
|
||||
Topic.validates_numericality_of :approved, even: true
|
||||
|
||||
invalid!([-1, 1], "must be even")
|
||||
valid!([-2, 2])
|
||||
assert_invalid_values([-1, 1], "must be even")
|
||||
assert_valid_values([-2, 2])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_greater_than_less_than_and_even
|
||||
Topic.validates_numericality_of :approved, greater_than: 1, less_than: 4, even: true
|
||||
|
||||
invalid!([1, 3, 4])
|
||||
valid!([2])
|
||||
assert_invalid_values([1, 3, 4])
|
||||
assert_valid_values([2])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_other_than
|
||||
Topic.validates_numericality_of :approved, other_than: 0
|
||||
|
||||
invalid!([0, 0.0])
|
||||
valid!([-1, 42])
|
||||
assert_invalid_values([0, 0.0])
|
||||
assert_valid_values([-1, 42])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_in
|
||||
Topic.validates_numericality_of :approved, in: 1..3
|
||||
|
||||
invalid!([0, 4])
|
||||
valid!([1, 2, 3])
|
||||
assert_invalid_values([0, 4])
|
||||
assert_valid_values([1, 2, 3])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_other_than_using_string_value
|
||||
Topic.validates_numericality_of :approved, other_than: 0
|
||||
|
||||
invalid!(["0", "0.0"])
|
||||
valid!(["-1", "1.1", "42"])
|
||||
assert_invalid_values(["0", "0.0"])
|
||||
assert_valid_values(["-1", "1.1", "42"])
|
||||
end
|
||||
|
||||
def test_validates_numericality_with_proc
|
||||
Topic.define_method(:min_approved) { 5 }
|
||||
Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new(&:min_approved)
|
||||
|
||||
invalid!([3, 4])
|
||||
valid!([5, 6])
|
||||
assert_invalid_values([3, 4])
|
||||
assert_valid_values([5, 6])
|
||||
ensure
|
||||
Topic.remove_method :min_approved
|
||||
end
|
||||
|
@ -235,8 +235,8 @@ class NumericalityValidationTest < ActiveModel::TestCase
|
|||
Topic.define_method(:max_approved) { 5 }
|
||||
Topic.validates_numericality_of :approved, less_than_or_equal_to: :max_approved
|
||||
|
||||
invalid!([6])
|
||||
valid!([4, 5])
|
||||
assert_invalid_values([6])
|
||||
assert_valid_values([4, 5])
|
||||
ensure
|
||||
Topic.remove_method :max_approved
|
||||
end
|
||||
|
@ -313,12 +313,12 @@ class NumericalityValidationTest < ActiveModel::TestCase
|
|||
def test_validates_numericality_equality_for_float_and_big_decimal
|
||||
Topic.validates_numericality_of :approved, equal_to: BigDecimal("65.6")
|
||||
|
||||
invalid!([Float("65.5"), BigDecimal("65.7")], "must be equal to 65.6")
|
||||
valid!([Float("65.6"), BigDecimal("65.6")])
|
||||
assert_invalid_values([Float("65.5"), BigDecimal("65.7")], "must be equal to 65.6")
|
||||
assert_valid_values([Float("65.6"), BigDecimal("65.6")])
|
||||
end
|
||||
|
||||
private
|
||||
def invalid!(values, error = nil)
|
||||
def assert_invalid_values(values, error = nil)
|
||||
with_each_topic_approved_value(values) do |topic, value|
|
||||
assert topic.invalid?, "#{value.inspect} not rejected as a number"
|
||||
assert topic.errors[:approved].any?, "FAILED for #{value.inspect}"
|
||||
|
@ -326,7 +326,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def valid!(values)
|
||||
def assert_valid_values(values)
|
||||
with_each_topic_approved_value(values) do |topic, value|
|
||||
assert topic.valid?, "#{value.inspect} not accepted as a number with validation error: #{topic.errors[:approved].first}"
|
||||
end
|
||||
|
|
|
@ -387,6 +387,36 @@ end
|
|||
|
||||
The default error message for this helper is _"doesn't match confirmation"_.
|
||||
|
||||
### `comparison`
|
||||
|
||||
This check will validate a comparison between any two comparable values.
|
||||
The validator requires a compare option be supplied. Each option accepts a
|
||||
value, proc, or symbol. Any class that includes Comparable can be compared.
|
||||
|
||||
```ruby
|
||||
class Promotion < ApplicationRecord
|
||||
validates :start_date, comparison: { greater_than: :end_date }
|
||||
end
|
||||
```
|
||||
|
||||
These options are all supported:
|
||||
|
||||
* `:greater_than` - Specifies the value must be greater than the supplied
|
||||
value. The default error message for this option is _"must be greater than
|
||||
%{count}"_.
|
||||
* `:greater_than_or_equal_to` - Specifies the value must be greater than or
|
||||
equal to the supplied value. The default error message for this option is
|
||||
_"must be greater than or equal to %{count}"_.
|
||||
* `:equal_to` - Specifies the value must be equal to the supplied value. The
|
||||
default error message for this option is _"must be equal to %{count}"_.
|
||||
* `:less_than` - Specifies the value must be less than the supplied value. The
|
||||
default error message for this option is _"must be less than %{count}"_.
|
||||
* `:less_than_or_equal_to` - Specifies the value must be less than or equal to
|
||||
the supplied value. The default error message for this option is _"must be
|
||||
less than or equal to %{count}"_.
|
||||
* `:other_than` - Specifies the value must be other than the supplied value.
|
||||
The default error message for this option is _"must be other than %{count}"_.
|
||||
|
||||
### `exclusion`
|
||||
|
||||
This helper validates that the attributes' values are not included in a given
|
||||
|
|
Loading…
Reference in New Issue