Added the :if option to all validations that can either use a block or a...

Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1340 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 2bccdba9
*SVN*
* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples:
Conditional validations such as the following are made possible:
validates_numericality_of :income, :if => :employed?
Conditional validations can also solve the salted login generator problem:
validates_confirmation_of :password, :if => :new_password?
Using blocks:
validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
* Fixed use of construct_finder_sql when using :join #1288 [dwlt@dwlt.net]
* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 [Rick Olson]
......
......@@ -227,6 +227,29 @@ def validate_on_update(*methods, &block)
write_inheritable_set(:validate_on_update, methods)
end
def condition_block?(condition)
condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
end
# Determine from the given condition (whether a block, procedure, method or string)
# whether or not to validate the record. See #validates_each.
def evaluate_condition(condition, record)
case condition
when Symbol: record.send(condition)
when String: eval(condition, binding)
else
if condition_block?(condition)
condition.call(record)
else
raise(
ActiveRecordError,
"Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
"class implementing a static validation method"
)
end
end
end
# Validates each attribute against a block.
#
# class Person < ActiveRecord::Base
......@@ -238,16 +261,22 @@ def validate_on_update(*methods, &block)
# 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.
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
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
# Don't validate when there is an :if condition and that condition is false
unless options[:if] && !evaluate_condition(options[:if], record)
attrs.each do |attr|
value = record.send(attr)
next if value.nil? && options[:allow_nil]
yield record, attr, value
end
end
end
end
......@@ -271,6 +300,9 @@ def validates_each(*attrs)
# Configuration options:
# * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation")
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -297,6 +329,9 @@ def validates_confirmation_of(*attr_names)
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
# * <tt>accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
# makes it easy to relate to an HTML checkbox.
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_acceptance_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -313,6 +348,9 @@ def validates_acceptance_of(*attr_names)
# Configuration options:
# * <tt>message</tt> - A custom error message (default is: "has already been taken")
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_presence_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:empty], :on => :save }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -321,7 +359,9 @@ def validates_presence_of(*attr_names)
# while errors.add_on_empty can
attr_names.each do |attr_name|
send(validation_method(configuration[:on])) do |record|
record.errors.add_on_empty(attr_name,configuration[:message])
unless configuration[:if] and not evaluate_condition(configuration[:if], record)
record.errors.add_on_empty(attr_name,configuration[:message])
end
end
end
end
......@@ -350,6 +390,9 @@ def validates_presence_of(*attr_names)
# * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
# * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_length_of(*attrs)
# Merge given options with defaults.
options = DEFAULT_SIZE_VALIDATION_OPTIONS.dup
......@@ -408,6 +451,9 @@ def validates_length_of(*attrs)
# Configuration options:
# * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
# * <tt>scope</tt> - Ensures that the uniqueness is restricted to a condition of "scope = record.scope"
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_uniqueness_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -436,6 +482,9 @@ def validates_uniqueness_of(*attr_names)
# * <tt>message</tt> - A custom error message (default is: "is invalid")
# * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!)
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_format_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -458,6 +507,9 @@ def validates_format_of(*attr_names)
# * <tt>in</tt> - An enumerable object of available items
# * <tt>message</tt> - Specifies a customer error message (default is: "is not included in the list")
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_inclusion_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -482,6 +534,9 @@ def validates_inclusion_of(*attr_names)
# * <tt>in</tt> - An enumerable object of items that the value shouldn't be part of
# * <tt>message</tt> - Specifies a customer error message (default is: "is reserved")
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_exclusion_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -516,6 +571,9 @@ def validates_exclusion_of(*attr_names)
#
# Configuration options:
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_associated(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
......@@ -539,6 +597,9 @@ def validates_associated(*attr_names)
# * <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)
# * <tt>allow_nil</tt> Skip validation if attribute is nil (default is false). Notice that for fixnum and float columsn empty strings are converted to nil
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
# method, proc or string should return or evaluate to a true or false value.
def validates_numericality_of(*attr_names)
configuration = { :message => ActiveRecord::Errors.default_error_messages[:not_a_number], :on => :save,
:only_integer => false, :allow_nil => false }
......
......@@ -3,6 +3,17 @@
require 'fixtures/reply'
require 'fixtures/developer'
# The following methods in Topic are used in test_conditional_validation_*
class Topic
def condition_is_true
return true
end
def condition_is_true_but_its_not
return false
end
end
class ValidationsTest < Test::Unit::TestCase
fixtures :topics, :developers
......@@ -688,7 +699,7 @@ def test_validates_numericality_of
#assert_in_delta v.to_f, t.approved, 0.0000001
end
end
def test_validates_numericality_of_int_with_string
Topic.validates_numericality_of( :approved, :only_integer => true )
["not a number","42 not a number","0xdeadbeef","0-1","--3","+-3","+3-1",nil].each do |v|
......@@ -697,7 +708,7 @@ def test_validates_numericality_of_int_with_string
assert t.errors.on(:approved)
end
end
def test_validates_numericality_of_int
Topic.validates_numericality_of( :approved, :only_integer => true, :allow_nil => true )
["42", "+42", "-42", "042", "0042", "-042", 42, nil,""].each do |v|
......@@ -707,4 +718,56 @@ def test_validates_numericality_of_int
end
end
def test_conditional_validation_using_method_true
# When the method returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
end
def test_conditional_validation_using_method_false
# When the method returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
end
def test_conditional_validation_using_string_true
# When the evaluated string returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "a = 1; a == 1" )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
end
def test_conditional_validation_using_string_false
# When the evaluated string returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "false")
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
end
def test_conditional_validation_using_block_true
# When the block returns true
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
:if => Proc.new { |r| r.content.size > 4 } )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert !t.valid?
assert t.errors.on(:title)
assert_equal "hoo 5", t.errors["title"]
end
def test_conditional_validation_using_block_false
# When the block returns false
Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
:if => Proc.new { |r| r.title != "uhohuhoh"} )
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert !t.errors.on(:title)
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册