mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add validates method as shortcut to setup validators for a given set of attributes:
class Person < ActiveRecord::Base include MyValidators validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 } validates :email, :presence => true, :email => true end [#3058 status:resolved] Signed-off-by: José Valim <jose.valim@gmail.com>
This commit is contained in:
parent
2dcc53bdbc
commit
0a79eb7889
17 changed files with 328 additions and 98 deletions
|
@ -15,21 +15,26 @@ module ActiveModel
|
|||
module ClassMethods
|
||||
# Validates each attribute against a block.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# class Person
|
||||
# include ActiveModel::Validations
|
||||
#
|
||||
# validates_each :first_name, :last_name do |record, attr, value|
|
||||
# record.errors.add attr, 'starts with z.' if value[0] == ?z
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Options:
|
||||
# * <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 (default is <tt>:save</tt>,
|
||||
# other options <tt>:create</tt>, <tt>:update</tt>).
|
||||
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
|
||||
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
|
||||
# * <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
|
||||
# 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.
|
||||
# * <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
|
||||
# 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_each(*attr_names, &block)
|
||||
options = attr_names.extract_options!.symbolize_keys
|
||||
|
@ -42,7 +47,9 @@ module ActiveModel
|
|||
#
|
||||
# This can be done with a symbol pointing to a method:
|
||||
#
|
||||
# class Comment < ActiveRecord::Base
|
||||
# class Comment
|
||||
# include ActiveModel::Validations
|
||||
#
|
||||
# validate :must_be_friends
|
||||
#
|
||||
# def must_be_friends
|
||||
|
@ -52,7 +59,9 @@ module ActiveModel
|
|||
#
|
||||
# Or with a block which is passed the current record to be validated:
|
||||
#
|
||||
# class Comment < ActiveRecord::Base
|
||||
# class Comment
|
||||
# include ActiveModel::Validations
|
||||
#
|
||||
# validate do |comment|
|
||||
# comment.must_be_friends
|
||||
# end
|
||||
|
@ -71,6 +80,13 @@ module ActiveModel
|
|||
end
|
||||
set_callback(:validate, *args, &block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _merge_attributes(attr_names)
|
||||
options = attr_names.extract_options!
|
||||
options.merge(:attributes => attr_names)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Errors object that holds all information about attribute error messages.
|
||||
|
@ -90,27 +106,24 @@ module ActiveModel
|
|||
!valid?
|
||||
end
|
||||
|
||||
protected
|
||||
# Hook method defining how an attribute value should be retieved. By default this is assumed
|
||||
# to be an instance named after the attribute. Override this method in subclasses should you
|
||||
# need to retrieve the value for a given attribute differently e.g.
|
||||
# class MyClass
|
||||
# include ActiveModel::Validations
|
||||
#
|
||||
# def initialize(data = {})
|
||||
# @data = data
|
||||
# end
|
||||
#
|
||||
# private
|
||||
#
|
||||
# def read_attribute_for_validation(key)
|
||||
# @data[key]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def read_attribute_for_validation(key)
|
||||
send(key)
|
||||
end
|
||||
# Hook method defining how an attribute value should be retieved. By default this is assumed
|
||||
# to be an instance named after the attribute. Override this method in subclasses should you
|
||||
# need to retrieve the value for a given attribute differently e.g.
|
||||
# class MyClass
|
||||
# include ActiveModel::Validations
|
||||
#
|
||||
# def initialize(data = {})
|
||||
# @data = data
|
||||
# end
|
||||
#
|
||||
# def read_attribute_for_validation(key)
|
||||
# @data[key]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def read_attribute_for_validation(key)
|
||||
send(key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,13 @@ module ActiveModel
|
|||
record.errors.add(attribute, :accepted, :default => options[:message])
|
||||
end
|
||||
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.
|
||||
new_attributes = attributes.reject { |name| klass.instance_methods.map(&:to_s).include?("#{name}=") }
|
||||
klass.send(:attr_accessor, *new_attributes)
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -37,18 +44,7 @@ module ActiveModel
|
|||
# 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_acceptance_of(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
|
||||
db_cols = begin
|
||||
column_names
|
||||
rescue Exception # To ignore both statement and connection errors
|
||||
[]
|
||||
end
|
||||
|
||||
names = attr_names.reject { |name| db_cols.include?(name.to_s) }
|
||||
attr_accessor(*names)
|
||||
|
||||
validates_with AcceptanceValidator, options.merge(:attributes => attr_names)
|
||||
validates_with AcceptanceValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,10 @@ module ActiveModel
|
|||
return if confirmed.nil? || value == confirmed
|
||||
record.errors.add(attribute, :confirmation, :default => options[:message])
|
||||
end
|
||||
|
||||
def setup(klass)
|
||||
klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" })
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -38,9 +42,7 @@ module ActiveModel
|
|||
# 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)
|
||||
options = attr_names.extract_options!
|
||||
attr_accessor(*(attr_names.map { |n| :"#{n}_confirmation" }))
|
||||
validates_with ConfirmationValidator, options.merge(:attributes => attr_names)
|
||||
validates_with ConfirmationValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ module ActiveModel
|
|||
module Validations
|
||||
class ExclusionValidator < EachValidator
|
||||
def check_validity!
|
||||
options[:in] ||= options.delete(:within)
|
||||
raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
|
||||
":in option of the configuration hash" unless options[:in].respond_to?(:include?)
|
||||
end
|
||||
|
@ -33,9 +34,7 @@ module ActiveModel
|
|||
# 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_exclusion_of(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
options[:in] ||= options.delete(:within)
|
||||
validates_with ExclusionValidator, options.merge(:attributes => attr_names)
|
||||
validates_with ExclusionValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,20 @@ module ActiveModel
|
|||
record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
|
||||
end
|
||||
end
|
||||
|
||||
def check_validity!
|
||||
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
|
||||
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
|
||||
end
|
||||
|
||||
if options[:with] && !options[:with].is_a?(Regexp)
|
||||
raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
|
||||
end
|
||||
|
||||
if options[:without] && !options[:without].is_a?(Regexp)
|
||||
raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -43,21 +57,7 @@ module ActiveModel
|
|||
# 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_format_of(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
|
||||
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
|
||||
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
|
||||
end
|
||||
|
||||
if options[:with] && !options[:with].is_a?(Regexp)
|
||||
raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash"
|
||||
end
|
||||
|
||||
if options[:without] && !options[:without].is_a?(Regexp)
|
||||
raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash"
|
||||
end
|
||||
|
||||
validates_with FormatValidator, options.merge(:attributes => attr_names)
|
||||
validates_with FormatValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ module ActiveModel
|
|||
module Validations
|
||||
class InclusionValidator < EachValidator
|
||||
def check_validity!
|
||||
options[:in] ||= options.delete(:within)
|
||||
raise ArgumentError, "An object with the method include? is required must be supplied as the " <<
|
||||
":in option of the configuration hash" unless options[:in].respond_to?(:include?)
|
||||
end
|
||||
|
@ -33,9 +34,7 @@ module ActiveModel
|
|||
# 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_inclusion_of(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
options[:in] ||= options.delete(:within)
|
||||
validates_with InclusionValidator, options.merge(:attributes => attr_names)
|
||||
validates_with InclusionValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -107,8 +107,7 @@ module ActiveModel
|
|||
# count words as in above example.)
|
||||
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
|
||||
def validates_length_of(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
validates_with LengthValidator, options.merge(:attributes => attr_names)
|
||||
validates_with LengthValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
|
||||
alias_method :validates_size_of, :validates_length_of
|
||||
|
|
|
@ -103,8 +103,7 @@ module ActiveModel
|
|||
# end
|
||||
#
|
||||
def validates_numericality_of(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
validates_with NumericalityValidator, options.merge(:attributes => attr_names)
|
||||
validates_with NumericalityValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,8 +34,7 @@ module ActiveModel
|
|||
# The method, proc or string should return or evaluate to a true or false value.
|
||||
#
|
||||
def validates_presence_of(*attr_names)
|
||||
options = attr_names.extract_options!
|
||||
validates_with PresenceValidator, options.merge(:attributes => attr_names)
|
||||
validates_with PresenceValidator, _merge_attributes(attr_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
74
activemodel/lib/active_model/validations/validates.rb
Normal file
74
activemodel/lib/active_model/validations/validates.rb
Normal file
|
@ -0,0 +1,74 @@
|
|||
module ActiveModel
|
||||
module Validations
|
||||
module ClassMethods
|
||||
# This method is a shortcut to all default validators and any custom
|
||||
# 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
|
||||
# validates :password, :confirmation => true
|
||||
# validates :username, :exclusion => { :in => %w(admin superuser) }
|
||||
# validates :email, :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }
|
||||
# validates :age, :inclusion => { :in => 0..9 }
|
||||
# validates :first_name, :length => { :maximum => 30 }
|
||||
# validates :age, :numericality => true
|
||||
# validates :username, :presence => true
|
||||
# validates :username, :uniqueness => true
|
||||
#
|
||||
# The power of the +validates+ method comes when using cusom 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
|
||||
# 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 my also exist within the class being validated
|
||||
# allowing custom modules of validators to be included as needed e.g.
|
||||
#
|
||||
# module MyValidators
|
||||
# class TitleValidator < ActiveModel::EachValidator
|
||||
# def validate_each(record, attribute, value)
|
||||
# record.errors[attribute] << "must start with 'the'" unless =~ /^the/i
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class Film
|
||||
# include ActiveModel::Validations
|
||||
# include MyValidators
|
||||
#
|
||||
# validates :name, :title => true
|
||||
# end
|
||||
#
|
||||
def validates(*attributes)
|
||||
validations = attributes.extract_options!
|
||||
|
||||
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?
|
||||
|
||||
validations.each do |key, options|
|
||||
begin
|
||||
validator = const_get("#{key.to_s.camelize}Validator")
|
||||
rescue NameError
|
||||
raise ArgumentError, "Unknown validator: '#{key}'"
|
||||
end
|
||||
|
||||
validates_with(validator, (options == true ? {} : options).merge(:attributes => attributes))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,14 +2,16 @@ module ActiveModel
|
|||
module Validations
|
||||
module ClassMethods
|
||||
|
||||
# Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions.
|
||||
# Passes the record off to the class or classes specified and allows them
|
||||
# to add errors based on more complex conditions.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# class Person
|
||||
# include ActiveModel::Validations
|
||||
# validates_with MyValidator
|
||||
# end
|
||||
#
|
||||
# class MyValidator < ActiveRecord::Validator
|
||||
# def validate
|
||||
# class MyValidator < ActiveModel::Validator
|
||||
# def validate(record)
|
||||
# if some_complex_logic
|
||||
# record.errors[:base] << "This record is invalid"
|
||||
# end
|
||||
|
@ -23,37 +25,46 @@ module ActiveModel
|
|||
#
|
||||
# You may also pass it multiple classes, like so:
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# class Person
|
||||
# include ActiveModel::Validations
|
||||
# validates_with MyValidator, MyOtherValidator, :on => :create
|
||||
# end
|
||||
#
|
||||
# Configuration options:
|
||||
# * <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 if the validation should
|
||||
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
|
||||
# * <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
|
||||
# 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.
|
||||
# * <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>).
|
||||
# * <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.
|
||||
#
|
||||
# If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>:
|
||||
# If you pass any additional configuration options, they will be passed
|
||||
# to the class and available as <tt>options</tt>:
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# class Person
|
||||
# include ActiveModel::Validations
|
||||
# validates_with MyValidator, :my_custom_key => "my custom value"
|
||||
# end
|
||||
#
|
||||
# class MyValidator < ActiveRecord::Validator
|
||||
# def validate
|
||||
# class MyValidator < ActiveModel::Validator
|
||||
# def validate(record)
|
||||
# options[:my_custom_key] # => "my custom value"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
def validates_with(*args, &block)
|
||||
options = args.extract_options!
|
||||
args.each { |klass| validate(klass.new(options, &block), options) }
|
||||
args.each do |klass|
|
||||
validator = klass.new(options, &block)
|
||||
validator.setup(self) if validator.respond_to?(:setup)
|
||||
validate(validator, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
|
@ -1,12 +1,13 @@
|
|||
module ActiveModel #:nodoc:
|
||||
# A simple base class that can be used along with ActiveModel::Base.validates_with
|
||||
# A simple base class that can be used along with ActiveModel::Validations::ClassMethods.validates_with
|
||||
#
|
||||
# class Person < ActiveModel::Base
|
||||
# class Person
|
||||
# include ActiveModel::Validations
|
||||
# validates_with MyValidator
|
||||
# end
|
||||
#
|
||||
# class MyValidator < ActiveModel::Validator
|
||||
# def validate
|
||||
# def validate(record)
|
||||
# if some_complex_logic
|
||||
# record.errors[:base] = "This record is invalid"
|
||||
# end
|
||||
|
@ -18,10 +19,11 @@ module ActiveModel #:nodoc:
|
|||
# end
|
||||
# end
|
||||
#
|
||||
# Any class that inherits from ActiveModel::Validator will have access to <tt>record</tt>,
|
||||
# which is an instance of the record being validated, and must implement a method called <tt>validate</tt>.
|
||||
# Any class that inherits from ActiveModel::Validator must implement a method
|
||||
# called <tt>validate</tt> which accepts a <tt>record</tt>.
|
||||
#
|
||||
# class Person < ActiveModel::Base
|
||||
# class Person
|
||||
# include ActiveModel::Validations
|
||||
# validates_with MyValidator
|
||||
# end
|
||||
#
|
||||
|
@ -36,7 +38,7 @@ module ActiveModel #:nodoc:
|
|||
# from within the validators message
|
||||
#
|
||||
# class MyValidator < ActiveModel::Validator
|
||||
# def validate
|
||||
# def validate(record)
|
||||
# record.errors[:base] << "This is some custom error message"
|
||||
# record.errors[:first_name] << "This is some complex validation"
|
||||
# # etc...
|
||||
|
@ -51,13 +53,47 @@ module ActiveModel #:nodoc:
|
|||
# @my_custom_field = options[:field_name] || :first_name
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The easiest way to add custom validators for validating individual attributes
|
||||
# is with the convenient ActiveModel::EachValidator for example:
|
||||
#
|
||||
# class TitleValidator < ActiveModel::EachValidator
|
||||
# def validate_each(record, attribute, value)
|
||||
# record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless ['Mr.', 'Mrs.', 'Dr.'].include?(value)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This can now be used in combination with the +validates+ method
|
||||
# (see ActiveModel::Validations::ClassMethods.validates for more on this)
|
||||
#
|
||||
# class Person
|
||||
# include ActiveModel::Validations
|
||||
# attr_accessor :title
|
||||
#
|
||||
# validates :title, :presence => true, :title => true
|
||||
# end
|
||||
#
|
||||
# Validator may also define a +setup+ instance method which will get called
|
||||
# with the class that using that validator as it's argument. This can be
|
||||
# useful when there are prerequisites such as an attr_accessor being present
|
||||
# for example:
|
||||
#
|
||||
# class MyValidator < ActiveModel::Validator
|
||||
# def setup(klass)
|
||||
# klass.send :attr_accessor, :custom_attribute
|
||||
# end
|
||||
# end
|
||||
#
|
||||
class Validator
|
||||
attr_reader :options
|
||||
|
||||
# Accepts options that will be made availible through the +options+ reader.
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
# Override this method in subclasses with validation logic, adding errors
|
||||
# to the records +errors+ array where necessary.
|
||||
def validate(record)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
@ -70,7 +106,10 @@ module ActiveModel #:nodoc:
|
|||
# All ActiveModel validations are built on top of this Validator.
|
||||
class EachValidator < Validator
|
||||
attr_reader :attributes
|
||||
|
||||
|
||||
# Returns a new validator instance. All options will be available via the
|
||||
# +options+ reader, however the <tt>:attributes</tt> option will be removed
|
||||
# and instead be made available through the +attributes+ reader.
|
||||
def initialize(options)
|
||||
@attributes = Array(options.delete(:attributes))
|
||||
raise ":attributes cannot be blank" if @attributes.empty?
|
||||
|
@ -78,18 +117,26 @@ module ActiveModel #:nodoc:
|
|||
check_validity!
|
||||
end
|
||||
|
||||
# Performs validation on the supplied record. By default this will call
|
||||
# +validates_each+ to determine validity therefore subclasses should
|
||||
# override +validates_each+ with validation logic.
|
||||
def validate(record)
|
||||
attributes.each do |attribute|
|
||||
value = record.send(:read_attribute_for_validation, attribute)
|
||||
value = record.read_attribute_for_validation(attribute)
|
||||
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
||||
validate_each(record, attribute, value)
|
||||
end
|
||||
end
|
||||
|
||||
# Override this method in subclasses with the validation logic, adding
|
||||
# errors to the records +errors+ array where necessary.
|
||||
def validate_each(record, attribute, value)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# Hook method that gets called by the initializer allowing verification
|
||||
# that the arguments supplied are valid. You could for example raise an
|
||||
# ArgumentError when invalid options are supplied.
|
||||
def check_validity!
|
||||
end
|
||||
end
|
||||
|
@ -103,6 +150,8 @@ module ActiveModel #:nodoc:
|
|||
super
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
@block.call(record, attribute, value)
|
||||
end
|
||||
|
|
53
activemodel/test/cases/validations/validates_test.rb
Normal file
53
activemodel/test/cases/validations/validates_test.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
# encoding: utf-8
|
||||
require 'cases/helper'
|
||||
require 'models/person'
|
||||
require 'models/person_with_validator'
|
||||
require 'validators/email_validator'
|
||||
|
||||
class ValidatesTest < ActiveRecord::TestCase
|
||||
def test_validates_with_built_in_validation
|
||||
Person.validates :title, :numericality => true
|
||||
person = Person.new
|
||||
person.valid?
|
||||
assert person.errors[:title].include?('is not a number')
|
||||
end
|
||||
|
||||
def test_validates_with_built_in_validation_and_options
|
||||
Person.validates :title, :numericality => { :message => 'my custom message' }
|
||||
person = Person.new
|
||||
person.valid?
|
||||
assert person.errors[:title].include?('my custom message')
|
||||
end
|
||||
|
||||
def test_validates_with_validator_class
|
||||
Person.validates :karma, :email => true
|
||||
person = Person.new
|
||||
person.valid?
|
||||
assert person.errors[:karma].include?('is not an email')
|
||||
end
|
||||
|
||||
def test_validates_with_validator_class_and_options
|
||||
Person.validates :karma, :email => { :message => 'my custom message' }
|
||||
person = Person.new
|
||||
person.valid?
|
||||
assert person.errors[:karma].include?('my custom message')
|
||||
end
|
||||
|
||||
def test_validates_with_unknown_validator
|
||||
assert_raise(ArgumentError) { Person.validates :karma, :unknown => true }
|
||||
end
|
||||
|
||||
def test_validates_with_included_validator
|
||||
PersonWithValidator.validates :title, :presence => true
|
||||
person = PersonWithValidator.new
|
||||
person.valid?
|
||||
assert person.errors[:title].include?('Local validator')
|
||||
end
|
||||
|
||||
def test_validates_with_included_validator_and_options
|
||||
PersonWithValidator.validates :title, :presence => { :custom => ' please' }
|
||||
person = PersonWithValidator.new
|
||||
person.valid?
|
||||
assert person.errors[:title].include?('Local validator please')
|
||||
end
|
||||
end
|
|
@ -120,6 +120,28 @@ class ValidatesWithTest < ActiveRecord::TestCase
|
|||
Topic.validates_with(validator, :if => "1 == 1", :foo => :bar)
|
||||
assert topic.valid?
|
||||
end
|
||||
|
||||
test "calls setup method of validator passing in self when validator has setup method" do
|
||||
topic = Topic.new
|
||||
validator = stub_everything
|
||||
validator.stubs(:new).returns(validator)
|
||||
validator.stubs(:validate)
|
||||
validator.stubs(:respond_to?).with(:setup).returns(true)
|
||||
validator.expects(:setup).with(Topic).once
|
||||
Topic.validates_with(validator)
|
||||
assert topic.valid?
|
||||
end
|
||||
|
||||
test "doesn't call setup method of validator when validator has no setup method" do
|
||||
topic = Topic.new
|
||||
validator = stub_everything
|
||||
validator.stubs(:new).returns(validator)
|
||||
validator.stubs(:validate)
|
||||
validator.stubs(:respond_to?).with(:setup).returns(false)
|
||||
validator.expects(:setup).with(Topic).never
|
||||
Topic.validates_with(validator)
|
||||
assert topic.valid?
|
||||
end
|
||||
|
||||
test "validates_with with options" do
|
||||
Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name)
|
||||
|
|
|
@ -8,8 +8,6 @@ class CustomReader
|
|||
def []=(key, value)
|
||||
@data[key] = value
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_attribute_for_validation(key)
|
||||
@data[key]
|
||||
|
|
11
activemodel/test/models/person_with_validator.rb
Normal file
11
activemodel/test/models/person_with_validator.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class PersonWithValidator
|
||||
include ActiveModel::Validations
|
||||
|
||||
class PresenceValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
record.errors[attribute] << "Local validator#{options[:custom]}" if value.blank?
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :title, :karma
|
||||
end
|
6
activemodel/test/validators/email_validator.rb
Normal file
6
activemodel/test/validators/email_validator.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
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
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue