aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/validations/numericality.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model/validations/numericality.rb')
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb148
1 files changed, 148 insertions, 0 deletions
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
new file mode 100644
index 0000000000..5bd162433d
--- /dev/null
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -0,0 +1,148 @@
+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: :!= }.freeze
+
+ RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
+
+ def check_validity!
+ keys = CHECKS.keys - [:odd, :even]
+ options.slice(*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
+ end
+
+ def validate_each(record, attr_name, value)
+ before_type_cast = :"#{attr_name}_before_type_cast"
+
+ raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
+ raw_value ||= value
+
+ return if options[:allow_nil] && raw_value.nil?
+
+ unless value = parse_raw_value_as_a_number(raw_value)
+ record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
+ return
+ end
+
+ if allow_only_integer?(record)
+ unless value = parse_raw_value_as_an_integer(raw_value)
+ record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
+ return
+ end
+ end
+
+ options.slice(*CHECKS.keys).each do |option, option_value|
+ case option
+ when :odd, :even
+ unless value.to_i.send(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)
+ end
+
+ unless value.send(CHECKS[option], option_value)
+ record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value))
+ end
+ end
+ end
+ end
+
+ protected
+
+ def parse_raw_value_as_a_number(raw_value)
+ Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
+ rescue ArgumentError, TypeError
+ nil
+ end
+
+ def parse_raw_value_as_an_integer(raw_value)
+ raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
+ end
+
+ def filtered_options(value)
+ filtered = options.except(*RESERVED_OPTIONS)
+ filtered[:value] = value
+ filtered
+ end
+
+ def allow_only_integer?(record)
+ case options[:only_integer]
+ when Symbol
+ record.send(options[:only_integer])
+ when Proc
+ options[:only_integer].call(record)
+ else
+ options[:only_integer]
+ end
+ end
+ end
+
+ module HelperMethods
+ # 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+).
+ #
+ # class Person < ActiveRecord::Base
+ # validates_numericality_of :value, on: :create
+ # end
+ #
+ # Configuration options:
+ # * <tt>:message</tt> - A custom error message (default is: "is not a number").
+ # * <tt>:only_integer</tt> - Specifies whether the value has to be an
+ # integer, e.g. an integral value (default is +false+).
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
+ # +false+). Notice that for fixnum and float columns empty strings are
+ # converted to +nil+.
+ # * <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 be other than the
+ # supplied value.
+ # * <tt>:odd</tt> - Specifies the value must be an odd number.
+ # * <tt>:even</tt> - Specifies the value must be an even number.
+ #
+ # There is also a list of default options supported by every validator:
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
+ # See <tt>ActiveModel::Validation#validates</tt> for more information
+ #
+ # The following checks can also be supplied with a proc 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>:only_integer</tt>
+ #
+ # For example:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_numericality_of :width, less_than: ->(person) { person.height }
+ # validates_numericality_of :width, greater_than: :minimum_weight
+ # end
+ def validates_numericality_of(*attr_names)
+ validates_with NumericalityValidator, _merge_attributes(attr_names)
+ end
+ end
+ end
+end