aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2009-12-23 00:36:51 +0100
committerJosé Valim <jose.valim@gmail.com>2009-12-23 00:36:51 +0100
commitf1085f41287687835659fa23079080204fe32e96 (patch)
tree8d8899c556c2b4aec9e377f842c15fe0a51416fd /activemodel/lib
parent2476c5312dcbd29f49672f71617a3d34c6a60cc7 (diff)
downloadrails-f1085f41287687835659fa23079080204fe32e96.tar.gz
rails-f1085f41287687835659fa23079080204fe32e96.tar.bz2
rails-f1085f41287687835659fa23079080204fe32e96.zip
Move validations in ActiveModel to validators, however all validatity checks are still in the class method.
Diffstat (limited to 'activemodel/lib')
-rw-r--r--activemodel/lib/active_model.rb5
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb18
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb20
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb21
-rw-r--r--activemodel/lib/active_model/validations/format.rb28
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb21
-rw-r--r--activemodel/lib/active_model/validations/length.rb91
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb105
-rw-r--r--activemodel/lib/active_model/validations/presence.rb15
-rw-r--r--activemodel/lib/active_model/validations/with.rb5
-rw-r--r--activemodel/lib/active_model/validator.rb24
11 files changed, 198 insertions, 155 deletions
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index e0de27b96d..6aa80da23f 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -35,9 +35,9 @@ module ActiveModel
autoload :Dirty
autoload :Errors
autoload :Lint
- autoload :Name, 'active_model/naming'
+ autoload :Name, 'active_model/naming'
autoload :Naming
- autoload :Observer, 'active_model/observing'
+ autoload :Observer, 'active_model/observing'
autoload :Observing
autoload :Serialization
autoload :StateMachine
@@ -45,6 +45,7 @@ module ActiveModel
autoload :Validations
autoload :ValidationsRepairHelper
autoload :Validator
+ autoload :EachValidator, 'active_model/validator'
autoload :VERSION
module Serializers
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index b65c9b933d..a5de58cd41 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -1,5 +1,13 @@
module ActiveModel
module Validations
+ class AcceptanceValidator < EachValidator
+ def validate_each(record, attribute, value)
+ unless value == options[:accept]
+ record.errors.add(attribute, :accepted, :default => options[:message])
+ end
+ end
+ end
+
module ClassMethods
# Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
#
@@ -25,8 +33,8 @@ 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)
- configuration = { :allow_nil => true, :accept => "1" }
- configuration.update(attr_names.extract_options!)
+ options = { :allow_nil => true, :accept => "1" }
+ options.update(attr_names.extract_options!)
db_cols = begin
column_names
@@ -37,11 +45,7 @@ module ActiveModel
names = attr_names.reject { |name| db_cols.include?(name.to_s) }
attr_accessor(*names)
- validates_each(attr_names,configuration) do |record, attr_name, value|
- unless value == configuration[:accept]
- record.errors.add(attr_name, :accepted, :default => configuration[:message])
- end
- end
+ validates_with AcceptanceValidator, options.merge(:attributes => attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index d414224dd2..b06effdceb 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -1,5 +1,13 @@
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, :default => options[:message])
+ end
+ end
+
module ClassMethods
# Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
#
@@ -30,15 +38,9 @@ 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)
- configuration = attr_names.extract_options!
-
- attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
-
- validates_each(attr_names, configuration) do |record, attr_name, value|
- unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
- record.errors.add(attr_name, :confirmation, :default => configuration[:message])
- end
- end
+ options = attr_names.extract_options!
+ attr_accessor(*(attr_names.map { |n| :"#{n}_confirmation" }))
+ validates_with ConfirmationValidator, options.merge(:attributes => attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 2cfdec97a5..209b1c76f9 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,5 +1,12 @@
module ActiveModel
module Validations
+ class ExclusionValidator < EachValidator
+ def validate_each(record, attribute, value)
+ return unless options[:in].include?(value)
+ record.errors.add(attribute, :exclusion, :default => options[:message], :value => value)
+ end
+ end
+
module ClassMethods
# Validates that the value of the specified attribute is not in a particular enumerable object.
#
@@ -21,17 +28,13 @@ 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)
- configuration = attr_names.extract_options!
-
- enum = configuration[:in] || configuration[:within]
+ options = attr_names.extract_options!
+ 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 enum.respond_to?(:include?)
+ 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?)
- validates_each(attr_names, configuration) do |record, attr_name, value|
- if enum.include?(value)
- record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
- end
- end
+ validates_with ExclusionValidator, options.merge(:attributes => attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index c670dafc7c..d5427c2b03 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,5 +1,15 @@
module ActiveModel
module Validations
+ class FormatValidator < EachValidator
+ def validate_each(record, attribute, value)
+ if options[:with] && value.to_s !~ options[:with]
+ record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ elsif options[:without] && value.to_s =~ options[:without]
+ record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ end
+ end
+ end
+
module ClassMethods
# Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
# You can require that the attribute matches the regular expression:
@@ -33,29 +43,21 @@ 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)
- configuration = attr_names.extract_options!
+ options = attr_names.extract_options!
- unless configuration.include?(:with) ^ configuration.include?(:without) # ^ == xor, or "exclusive or"
+ 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 configuration[:with] && !configuration[:with].is_a?(Regexp)
+ 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 configuration[:without] && !configuration[:without].is_a?(Regexp)
+ 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
- if configuration[:with]
- validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) if value.to_s !~ configuration[:with]
- end
- elsif configuration[:without]
- validates_each(attr_names, configuration) do |record, attr_name, value|
- record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value) if value.to_s =~ configuration[:without]
- end
- end
+ validates_with FormatValidator, options.merge(:attributes => attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 0d7dc5cd64..d42c95357c 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,5 +1,12 @@
module ActiveModel
module Validations
+ class InclusionValidator < EachValidator
+ def validate_each(record, attribute, value)
+ return if options[:in].include?(value)
+ record.errors.add(attribute, :inclusion, :default => options[:message], :value => value)
+ end
+ end
+
module ClassMethods
# Validates whether the value of the specified attribute is available in a particular enumerable object.
#
@@ -21,17 +28,13 @@ 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)
- configuration = attr_names.extract_options!
-
- enum = configuration[:in] || configuration[:within]
+ options = attr_names.extract_options!
+ 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 enum.respond_to?(:include?)
+ 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?)
- validates_each(attr_names, configuration) do |record, attr_name, value|
- unless enum.include?(value)
- record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
- end
- end
+ validates_with InclusionValidator, options.merge(:attributes => attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index e91841bd1c..66b2ae5b18 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,7 +1,44 @@
module ActiveModel
module Validations
+ class LengthValidator < EachValidator
+ MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
+ CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
+
+ attr_reader :type
+
+ def initialize(options)
+ @type = options.delete(:type)
+ super
+ end
+
+ def validate_each(record, attribute, value)
+ checks = options.slice(:minimum, :maximum, :is)
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
+
+ if [:within, :in].include?(type)
+ range = options[type]
+ checks[:minimum], checks[:maximum] = range.begin, range.end
+ checks[:maximum] -= 1 if range.exclude_end?
+ end
+
+ checks.each do |key, check_value|
+ custom_message = options[:message] || options[MESSAGES[key]]
+ validity_check = CHECKS[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
+
+ record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value) unless valid_value
+ end
+ end
+ end
+
module ClassMethods
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
+ DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
#
@@ -38,62 +75,32 @@ module ActiveModel
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
# count words as in above example.)
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
- def validates_length_of(*attrs)
- # Merge given options with defaults.
- options = { :tokenizer => lambda {|value| value.split(//)} }
- options.update(attrs.extract_options!.symbolize_keys)
+ def validates_length_of(*attr_names)
+ options = { :tokenizer => DEFAULT_TOKENIZER }
+ options.update(attr_names.extract_options!)
# Ensure that one and only one range option is specified.
range_options = ALL_RANGE_OPTIONS & options.keys
case range_options.size
when 0
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
when 1
# Valid number of options; do nothing.
else
- raise ArgumentError, 'Too many range options specified. Choose only one.'
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
end
- # Get range option and value.
- option = range_options.first
- option_value = options[range_options.first]
- key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
- custom_message = options[:message] || options[key]
+ type = range_options.first
+ value = options[type]
- case option
+ case type
when :within, :in
- raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
-
- validates_each(attrs, options) do |record, attr, value|
- value = options[:tokenizer].call(value) if value.kind_of?(String)
-
- min, max = option_value.begin, option_value.end
- max = max - 1 if option_value.exclude_end?
-
- if value.nil? || value.size < min
- record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => min)
- elsif value.size > max
- record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => max)
- end
- end
+ raise ArgumentError, ":#{type} must be a Range" unless value.is_a?(Range)
when :is, :minimum, :maximum
- raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
-
- # Declare different validations per option.
- validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
-
- validates_each(attrs, options) do |record, attr, value|
- value = options[:tokenizer].call(value) if value.kind_of?(String)
-
- valid_value = if option == :maximum
- value.nil? || value.size.send(validity_checks[option], option_value)
- else
- value && value.size.send(validity_checks[option], option_value)
- end
-
- record.errors.add(attr, key, :default => custom_message, :count => option_value) unless valid_value
- end
+ raise ArgumentError, ":#{type} must be a nonnegative Integer" unless value.is_a?(Integer) && value >= 0
end
+
+ validates_with LengthValidator, options.merge(:attributes => attr_names, :type => type)
end
alias_method :validates_size_of, :validates_length_of
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 32dbcd82d0..8ffa395a2d 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -1,9 +1,55 @@
module ActiveModel
module Validations
+ class NumericalityValidator < EachValidator
+ CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
+ :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
+ :odd => 'odd?', :even => 'even?' }.freeze
+
+ def validate_each(record, attr_name, value)
+ before_type_cast = "#{attr_name}_before_type_cast"
+
+ if record.respond_to?(before_type_cast.to_sym)
+ raw_value = record.send("#{attr_name}_before_type_cast") || value
+ else
+ raw_value = value
+ end
+
+ return if options[:allow_nil] and raw_value.nil?
+
+ if options[:only_integer]
+ unless raw_value.to_s =~ /\A[+-]?\d+\Z/
+ record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message])
+ return
+ end
+ raw_value = raw_value.to_i
+ else
+ begin
+ raw_value = Kernel.Float(raw_value)
+ rescue ArgumentError, TypeError
+ record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message])
+ return
+ end
+ end
+
+ options.slice(*CHECKS.keys).each do |option, option_value|
+ case option
+ when :odd, :even
+ unless raw_value.to_i.method(CHECKS[option])[]
+ record.errors.add(attr_name, option, :value => raw_value, :default => options[:message])
+ end
+ else
+ option_value = option_value.call(record) if option_value.is_a? Proc
+ option_value = record.method(option_value).call if option_value.is_a? Symbol
+
+ unless raw_value.method(CHECKS[option])[option_value]
+ record.errors.add(attr_name, option, :default => options[:message], :value => raw_value, :count => option_value)
+ end
+ end
+ end
+ end
+ end
module ClassMethods
- ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
- :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
- :odd => 'odd?', :even => 'even?' }.freeze
+
# 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
@@ -44,61 +90,18 @@ module ActiveModel
# validates_numericality_of :width, :greater_than => :minimum_weight
# end
#
- #
-
def validates_numericality_of(*attr_names)
- configuration = { :only_integer => false, :allow_nil => false }
- configuration.update(attr_names.extract_options!)
+ options = { :only_integer => false, :allow_nil => false }
+ options.update(attr_names.extract_options!)
- numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
+ numericality_options = NumericalityValidator::CHECKS.keys & options.keys
(numericality_options - [ :odd, :even ]).each do |option|
- value = configuration[option]
+ value = options[option]
raise ArgumentError, ":#{option} must be a number, a symbol or a proc" unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
end
- validates_each(attr_names,configuration) do |record, attr_name, value|
- before_type_cast = "#{attr_name}_before_type_cast"
-
- if record.respond_to?(before_type_cast.to_sym)
- raw_value = record.send("#{attr_name}_before_type_cast") || value
- else
- raw_value = value
- end
-
- next if configuration[:allow_nil] and raw_value.nil?
-
- if configuration[:only_integer]
- unless raw_value.to_s =~ /\A[+-]?\d+\Z/
- record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- next
- end
- raw_value = raw_value.to_i
- else
- begin
- raw_value = Kernel.Float(raw_value)
- rescue ArgumentError, TypeError
- record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
- next
- end
- end
-
- numericality_options.each do |option|
- case option
- when :odd, :even
- unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
- record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
- end
- else
- configuration[option] = configuration[option].call(record) if configuration[option].is_a? Proc
- configuration[option] = record.method(configuration[option]).call if configuration[option].is_a? Symbol
-
- unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
- record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option])
- end
- end
- end
- end
+ validates_with NumericalityValidator, options.merge(:attributes => attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 3ff677c137..a4c6f866a7 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -2,6 +2,12 @@ require 'active_support/core_ext/object/blank'
module ActiveModel
module Validations
+ class PresenceValidator < EachValidator
+ def validate(record)
+ record.errors.add_on_blank(attributes, options[:message])
+ end
+ end
+
module ClassMethods
# Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
#
@@ -28,13 +34,8 @@ module ActiveModel
# The method, proc or string should return or evaluate to a true or false value.
#
def validates_presence_of(*attr_names)
- configuration = attr_names.extract_options!
-
- # can't use validates_each here, because it cannot cope with nonexistent attributes,
- # while errors.add_on_empty can
- validate configuration do |record|
- record.errors.add_on_blank(attr_names, configuration[:message])
- end
+ options = attr_names.extract_options!
+ validates_with PresenceValidator, options.merge(:attributes => attr_names)
end
end
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 8313aded11..626e9d5731 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -50,10 +50,7 @@ module ActiveModel
#
def validates_with(*args)
options = args.extract_options!
-
- args.each do |klass|
- validate klass.new(options.except(:on, :if, :unless)), options
- end
+ args.each { |klass| validate(klass.new(options), options) }
end
end
end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 80a538fcb6..6885c6800e 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,5 +1,4 @@
module ActiveModel #:nodoc:
-
# A simple base class that can be used along with ActiveModel::Base.validates_with
#
# class Person < ActiveModel::Base
@@ -61,7 +60,28 @@ module ActiveModel #:nodoc:
end
def validate(record)
- raise "You must override this method"
+ raise NotImplementedError
+ end
+ end
+
+ class EachValidator < Validator
+ attr_reader :attributes
+
+ def initialize(options)
+ @attributes = options.delete(:attributes)
+ super
+ end
+
+ def validate(record)
+ attributes.each do |attribute|
+ value = record.send(:read_attribute_for_validation, attribute)
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
+ validate_each(record, attribute, value)
+ end
+ end
+
+ def validate_each(record)
+ raise NotImplementedError
end
end
end