mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
Added validates_numericality_of #716 [skanthak/c.r.mcgrath]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@842 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
*SVN*
|
||||
|
||||
* Added validates_numericality_of #716 [skanthak/c.r.mcgrath]. Docuemntation:
|
||||
|
||||
Validates whether the value of the specified attribute is numeric by trying to convert it to
|
||||
a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
|
||||
<tt>/^[\+\-]?\d+$/</tt> (if <tt>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>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
||||
* <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
|
||||
|
||||
|
||||
* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 [Scott Barron]
|
||||
|
||||
* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed)
|
||||
|
||||
@@ -16,6 +16,7 @@ module ActiveRecord
|
||||
:too_short => "is too short (min is %d characters)",
|
||||
:wrong_length => "is the wrong length (should be %d characters)",
|
||||
:taken => "has already been taken",
|
||||
:not_a_number => "is not a number",
|
||||
}
|
||||
|
||||
# Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
|
||||
@@ -193,6 +194,20 @@ module ActiveRecord
|
||||
# They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
|
||||
# these over the low-level calls to validate and validate_on_create when possible.
|
||||
module ClassMethods
|
||||
DEFAULT_VALIDATION_OPTIONS = {
|
||||
:on => :save,
|
||||
:allow_nil => false,
|
||||
:message => nil
|
||||
}.freeze
|
||||
|
||||
DEFAULT_SIZE_VALIDATION_OPTIONS = DEFAULT_VALIDATION_OPTIONS.merge(
|
||||
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
|
||||
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
|
||||
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
|
||||
).freeze
|
||||
|
||||
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
|
||||
|
||||
def validate(*methods, &block)
|
||||
methods << block if block_given?
|
||||
write_inheritable_set(:validate, methods)
|
||||
@@ -208,6 +223,31 @@ module ActiveRecord
|
||||
write_inheritable_set(:validate_on_update, methods)
|
||||
end
|
||||
|
||||
# Validates each attribute against a block.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_each :first_name, :last_name do |record, attr|
|
||||
# record.errors.add attr, 'starts with z.' if attr[0] == ?z
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Options:
|
||||
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
||||
# * <tt>allow_nil</tt> - Skip validation if attribute is nil.
|
||||
def validates_each(*attrs)
|
||||
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
|
||||
attrs = attrs.flatten
|
||||
|
||||
# Declare the validation.
|
||||
send(validation_method(options[:on] || :save)) do |record|
|
||||
attrs.each do |attr|
|
||||
value = record.send(attr)
|
||||
next if value.nil? && options[:allow_nil]
|
||||
yield record, attr, value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
|
||||
#
|
||||
# Model:
|
||||
@@ -279,48 +319,6 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
DEFAULT_VALIDATION_OPTIONS = {
|
||||
:on => :save,
|
||||
:allow_nil => false,
|
||||
:message => nil
|
||||
}.freeze
|
||||
|
||||
DEFAULT_SIZE_VALIDATION_OPTIONS = DEFAULT_VALIDATION_OPTIONS.merge(
|
||||
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
|
||||
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
|
||||
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
|
||||
).freeze
|
||||
|
||||
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
|
||||
|
||||
|
||||
# Validates each attribute against a block.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_each :first_name, :last_name do |record, attr|
|
||||
# record.errors.add attr, 'starts with z.' if attr[0] == ?z
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Options:
|
||||
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
||||
# * <tt>allow_nil</tt> - Skip validation if attribute is nil.
|
||||
def validates_each(*attrs)
|
||||
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
|
||||
attrs = attrs.flatten
|
||||
|
||||
# Declare the validation.
|
||||
send(validation_method(options[:on] || :save)) do |record|
|
||||
attrs.each do |attr|
|
||||
value = record.send(attr)
|
||||
next if value.nil? && options[:allow_nil]
|
||||
yield record, attr, value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
@@ -516,6 +514,42 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
# Validates whether the value of the specified attribute is numeric by trying to convert it to
|
||||
# a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
|
||||
# <tt>/^[\+\-]?\d+$/</tt> (if <tt>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>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
||||
# * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
|
||||
def validates_numericality_of(*attr_names)
|
||||
configuration = { :message => ActiveRecord::Errors.default_error_messages[:not_a_number], :on => :save,
|
||||
:integer => false }
|
||||
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
||||
|
||||
for attr_name in attr_names
|
||||
if configuration[:only_integer]
|
||||
# we have to use a regexp here, because Kernel.Integer accepts nil and "0xdeadbeef", but does not
|
||||
# accept "099" and String#to_i accepts everything. The string containing the regexp is evaluated twice
|
||||
# so we have to escape everything properly
|
||||
class_eval(%(#{validation_method(configuration[:on])} %{
|
||||
errors.add("#{attr_name}", "#{configuration[:message]}") unless #{attr_name}_before_type_cast.to_s =~ /^[\\\\+\\\\-]?\\\\d+$/
|
||||
}))
|
||||
else
|
||||
class_eval(%(#{validation_method(configuration[:on])} %{
|
||||
begin
|
||||
Kernel.Float(#{attr_name}_before_type_cast)
|
||||
rescue ArgumentError, TypeError
|
||||
errors.add("#{attr_name}", "#{configuration[:message]}")
|
||||
end
|
||||
}))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def write_inheritable_set(key, methods)
|
||||
|
||||
@@ -604,4 +604,42 @@ class ValidationsTest < Test::Unit::TestCase
|
||||
assert !r.valid?
|
||||
assert_equal r.errors.on(:topic).first, "This string contains 'single' and \"double\" quotes"
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_with_string
|
||||
Topic.validates_numericality_of( :replies_count )
|
||||
["not a number","42 not a number","0xdeadbeef","00-1","-+019.0","12.12.13.12",nil].each do |v|
|
||||
t = Topic.create("title" => "numeric test", "content" => "whatever", "replies_count" => "not a number")
|
||||
assert !t.valid?, "#{v} not rejected as a number"
|
||||
assert t.errors.on(:replies_count)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_numericality_of
|
||||
Topic.validates_numericality_of( :replies_count )
|
||||
["10", "10.0", "10.5", "-10.5", "-0.0001","0090","-090","-090.1"].each do |v|
|
||||
t = Topic.create("title" => "numeric test", "content" => "whatever", "replies_count" => v)
|
||||
assert t.valid?, "#{v} not recognized as a number"
|
||||
# we cannot check this as replies_count is actually an integer field
|
||||
#assert_in_delta v.to_f, t.replies_count, 0.0000001
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_int_with_string
|
||||
Topic.validates_numericality_of( :replies_count, :only_integer => true )
|
||||
["not a number","42 not a number","0xdeadbeef","0-1","--3","+-3","+3-1",nil].each do |v|
|
||||
t = Topic.create("title" => "numeric test", "content" => "whatever", "replies_count" => v)
|
||||
assert !t.valid?, "#{v} not rejected as integer"
|
||||
assert t.errors.on(:replies_count)
|
||||
end
|
||||
end
|
||||
|
||||
def test_validates_numericality_of_int
|
||||
Topic.validates_numericality_of( :replies_count, :only_integer => true )
|
||||
["42", "+42", "-42", "042", "0042", "-042", 42].each do |v|
|
||||
t = Topic.create("title" => "numeric test", "content" => "whatever", "replies_count" => v)
|
||||
assert t.valid?, "#{v} not recognized as integer"
|
||||
assert_equal v.to_i, t.replies_count
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user