aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/validations
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model/validations')
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb28
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb6
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb35
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb3
-rw-r--r--activemodel/lib/active_model/validations/format.rb4
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb18
-rw-r--r--activemodel/lib/active_model/validations/length.rb15
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb6
-rw-r--r--activemodel/lib/active_model/validations/presence.rb5
-rw-r--r--activemodel/lib/active_model/validations/validates.rb48
-rw-r--r--activemodel/lib/active_model/validations/with.rb16
11 files changed, 116 insertions, 68 deletions
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 99b8966def..4f390613aa 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -14,8 +14,6 @@ module ActiveModel
end
def setup(klass)
- # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility
- # as instance_methods returns symbols unlike 1.8 which returns strings.
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
klass.send(:attr_reader, *attr_readers)
@@ -24,7 +22,7 @@ module ActiveModel
end
module HelperMethods
- # Encapsulates the pattern of wanting to validate the acceptance of a
+ # Encapsulates the pattern of wanting to validate the acceptance of a
# terms of service check box (or similar agreement). Example:
#
# class Person < ActiveRecord::Base
@@ -32,33 +30,33 @@ module ActiveModel
# validates_acceptance_of :eula, :message => "must be abided"
# end
#
- # If the database column does not exist, the +terms_of_service+ attribute
+ # If the database column does not exist, the +terms_of_service+ attribute
# is entirely virtual. This check is performed only if +terms_of_service+
# is not +nil+ and by default on save.
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "must be
+ # * <tt>:message</tt> - A custom error message (default is: "must be
# accepted").
- # * <tt>:on</tt> - Specifies when this validation is active (default is
- # <tt>:save</tt>, other options are <tt>:create</tt> and
- # <tt>:update</tt>).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
# is true).
- # * <tt>:accept</tt> - Specifies value that is considered accepted.
+ # * <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. This should be set to +true+ if you are validating
+ # an HTML checkbox. This should be set to +true+ if you are validating
# a database column, since the attribute is typecast from "1" to +true+
# before validation.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
# or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false
+ # method, proc or string should return or evaluate to a true or false
# value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to
- # determine if the validation should not occur (for example,
- # <tt>:unless => :skip_validation</tt>, or
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
+ # determine if the validation should not occur (for example,
+ # <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or
+ # The method, proc or string should return or evaluate to a true or
# false value.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index afd65d3dd5..adc2867ad0 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -14,7 +14,7 @@ module ActiveModel
# include ActiveModel::Validations::Callbacks
#
# before_validation :do_stuff_before_validation
- # after_validation :do_tuff_after_validation
+ # after_validation :do_stuff_after_validation
# end
#
# Like other before_* callbacks if <tt>before_validation</tt> returns false
@@ -40,7 +40,7 @@ module ActiveModel
options = args.extract_options!
options[:prepend] = true
options[:if] = Array.wrap(options[:if])
- options[:if] << "!halted && value != false"
+ options[:if] << "!halted"
options[:if] << "self.validation_context == :#{options[:on]}" if options[:on]
set_callback(:validation, :after, *(args << options), &block)
end
@@ -50,7 +50,7 @@ module ActiveModel
# Overwrite run validations to include callbacks.
def run_validations!
- _run_validation_callbacks { super }
+ run_callbacks(:validation) { super }
end
end
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 3a80893866..e6d10cfff8 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -4,24 +4,26 @@ module ActiveModel
module Validations
class ConfirmationValidator < EachValidator
def validate_each(record, attribute, value)
- confirmed = record.send(:"#{attribute}_confirmation")
- return if confirmed.nil? || value == confirmed
- record.errors.add(attribute, :confirmation, options)
+ if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
+ record.errors.add(attribute, :confirmation, options)
+ end
end
def setup(klass)
- klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" })
+ klass.send(:attr_accessor, *attributes.map do |attribute|
+ :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
+ end.compact)
end
end
module HelperMethods
- # Encapsulates the pattern of wanting to validate a password or email
+ # Encapsulates the pattern of wanting to validate a password or email
# address field with a confirmation. For example:
#
# Model:
# class Person < ActiveRecord::Base
# validates_confirmation_of :user_name, :password
- # validates_confirmation_of :email_address,
+ # validates_confirmation_of :email_address,
# :message => "should match confirmation"
# end
#
@@ -29,12 +31,12 @@ module ActiveModel
# <%= password_field "person", "password" %>
# <%= password_field "person", "password_confirmation" %>
#
- # The added +password_confirmation+ attribute is virtual; it exists only
+ # The added +password_confirmation+ attribute is virtual; it exists only
# as an in-memory attribute for validating the password. To achieve this,
- # the validation adds accessors to the model for the confirmation
+ # the validation adds accessors to the model for the confirmation
# attribute.
- #
- # NOTE: This check is performed only if +password_confirmation+ is not
+ #
+ # NOTE: This check is performed only if +password_confirmation+ is not
# +nil+, and by default only on save. To require confirmation, make sure
# to add a presence check for the confirmation attribute:
#
@@ -43,16 +45,17 @@ module ActiveModel
# 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
- # <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
# or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false
+ # method, proc or string should return or evaluate to a true or false
# value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to
- # determine if the validation should not occur (e.g.
- # <tt>:unless => :skip_validation</tt>, or
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
+ # determine if the validation should not occur (e.g.
+ # <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 4138892786..e38e565d09 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -29,6 +29,9 @@ module ActiveModel
# * <tt>:message</tt> - Specifies a custom error message (default is: "is reserved").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 104f403492..541f53a834 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -51,7 +51,9 @@ module ActiveModel
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:with</tt> - Regular expression that if the attribute matches will result in a successful validation.
# * <tt>:without</tt> - Regular expression that if the attribute does not match will result in a successful validation.
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 049b093618..92ac940f36 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/range.rb'
+
module ActiveModel
# == Active Model Inclusion Validator
@@ -9,9 +11,14 @@ module ActiveModel
end
def validate_each(record, attribute, value)
- unless options[:in].include?(value)
- record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
- end
+ record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value)) unless options[:in].send(include?, value)
+ end
+
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
+ # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
+ # uses the previous logic of comparing a value with the range endpoints.
+ def include?
+ options[:in].is_a?(Range) ? :cover? : :include?
end
end
@@ -26,9 +33,14 @@ module ActiveModel
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items.
+ # If the enumerable is a range the test is performed with <tt>Range#cover?</tt>
+ # (backported in Active Support for 1.8), otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom 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 +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index a7af4f2b4d..72735cfb89 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -34,20 +34,17 @@ module ActiveModel
end
end
end
-
+
def validate_each(record, attribute, value)
value = options[:tokenizer].call(value) if value.kind_of?(String)
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
- valid_value = if key == :maximum
- value.nil? || value.size.send(validity_check, check_value)
- else
- value && value.size.send(validity_check, check_value)
- end
+ value ||= [] if key == :maximum
- next if valid_value
+ value_length = value.respond_to?(:length) ? value.length : value.to_s.length
+ next if value_length.send(validity_check, check_value)
errors_options = options.except(*RESERVED_OPTIONS)
errors_options[:count] = check_value
@@ -87,7 +84,9 @@ module ActiveModel
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %{count} characters)").
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index b6aff7aa6b..ae576462e6 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -24,7 +24,7 @@ module ActiveModel
def validate_each(record, attr_name, value)
before_type_cast = "#{attr_name}_before_type_cast"
- raw_value = record.send("#{attr_name}_before_type_cast") if record.respond_to?(before_type_cast.to_sym)
+ raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast.to_sym)
raw_value ||= value
return if options[:allow_nil] && raw_value.nil?
@@ -93,7 +93,9 @@ module ActiveModel
#
# 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 <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <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.
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 28c4640b17..cfb4c33dcc 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -26,8 +26,9 @@ module ActiveModel
#
# Configuration options:
# * <tt>message</tt> - A custom error message (default is: "can't be blank").
- # * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
- # <tt>:update</tt>).
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
+ # and <tt>:update</tt>.
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 3260e6bc5a..7ff42de00b 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -9,7 +9,7 @@ module ActiveModel
# validator classes ending in 'Validator'. Note that Rails default
# validators can be overridden inside specific classes by creating
# custom validator classes in their place such as PresenceValidator.
- #
+ #
# Examples of using the default rails validators:
#
# validates :terms, :acceptance => true
@@ -21,25 +21,25 @@ module ActiveModel
# validates :age, :numericality => true
# validates :username, :presence => true
# validates :username, :uniqueness => true
- #
+ #
# The power of the +validates+ method comes when using custom validators
# and default validators in one call for a given attribute e.g.
#
# class EmailValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
# record.errors[attribute] << (options[:message] || "is not an email") unless
- # value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
+ # value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
# end
# end
- #
+ #
# class Person
# include ActiveModel::Validations
# attr_accessor :name, :email
- #
+ #
# validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
# validates :email, :presence => true, :email => true
# end
- #
+ #
# Validator classes may also exist within the class being validated
# allowing custom modules of validators to be included as needed e.g.
#
@@ -48,21 +48,30 @@ module ActiveModel
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << "must start with 'the'" unless =~ /^the/i
+ # record.errors[attribute] << "must start with 'the'" unless value =~ /\Athe/i
# end
# end
#
# validates :name, :title => true
# end
#
- # The validators hash can also handle regular expressions, ranges and arrays:
+ # Additionally validator classes may be in another namespace and still used within any class.
+ #
+ # validates :name, :'file/title' => true
+ #
+ # The validators hash can also handle regular expressions, ranges,
+ # arrays and strings in shortcut form, e.g.
#
# validates :email, :format => /@/
# validates :gender, :inclusion => %w(male female)
# validates :password, :length => 6..20
#
- # Finally, the options :if, :unless, :on, :allow_blank and :allow_nil can be given
- # to one specific validator:
+ # When using shortcut form, ranges and arrays are passed to your
+ # validator's initializer as +options[:in]+ while other types including
+ # regular expressions and strings are passed as +options[:with]+
+ #
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+ and +:allow_nil+ can be given
+ # to one specific validator, as a hash:
#
# validates :password, :presence => { :if => :password_required? }, :confirmation => true
#
@@ -72,17 +81,18 @@ module ActiveModel
#
def validates(*attributes)
defaults = attributes.extract_options!
- validations = defaults.slice!(:if, :unless, :on, :allow_blank, :allow_nil)
+ validations = defaults.slice!(*_validates_default_keys)
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
- raise ArgumentError, "Attribute names must be symbols" if attributes.any?{ |attribute| !attribute.is_a?(Symbol) }
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
defaults.merge!(:attributes => attributes)
validations.each do |key, options|
+ key = "#{key.to_s.camelize}Validator"
+
begin
- validator = const_get("#{key.to_s.camelize}Validator")
+ validator = key.include?('::') ? key.constantize : const_get(key)
rescue NameError
raise ArgumentError, "Unknown validator: '#{key}'"
end
@@ -93,18 +103,24 @@ module ActiveModel
protected
+ # When creating custom validators, it might be useful to be able to specify
+ # additional default keys. This can be done by overwriting this method.
+ def _validates_default_keys
+ [ :if, :unless, :on, :allow_blank, :allow_nil ]
+ end
+
def _parse_validates_options(options) #:nodoc:
case options
when TrueClass
{}
when Hash
options
- when Regexp
- { :with => options }
when Range, Array
{ :in => options }
+ else
+ { :with => options }
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 200efd4eb5..65ae18a769 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -8,6 +8,18 @@ module ActiveModel
end
end
+ class WithValidator < EachValidator
+ def validate_each(record, attr, val)
+ method_name = options[:with]
+
+ if record.method(method_name).arity == 0
+ record.send method_name
+ else
+ record.send method_name, attr
+ end
+ end
+ end
+
module ClassMethods
# Passes the record off to the class or classes specified and allows them
# to add errors based on more complex conditions.
@@ -38,9 +50,9 @@ module ActiveModel
# end
#
# Configuration options:
- # * <tt>on</tt> - Specifies when this validation is active
+ # * <tt>:on</tt> - Specifies when this validation is active
# (<tt>:create</tt> or <tt>:update</tt>
- # * <tt>if</tt> - Specifies a method, proc or string to call to determine
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
# or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.