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.rb20
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb8
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb4
-rw-r--r--activemodel/lib/active_model/validations/format.rb30
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb4
-rw-r--r--activemodel/lib/active_model/validations/length.rb69
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb10
-rw-r--r--activemodel/lib/active_model/validations/presence.rb3
-rw-r--r--activemodel/lib/active_model/validations/validates.rb106
-rw-r--r--activemodel/lib/active_model/validations/with.rb47
10 files changed, 196 insertions, 105 deletions
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index bd9463ed27..0423fcd17f 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -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
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index b06effdceb..8041d4b61f 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -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
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index f8759f253b..7ee718cf3c 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -33,9 +33,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
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index d5427c2b03..9a9e7eca4d 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -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
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index a122e9e737..0c1334fe1b 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -33,9 +33,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
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 5db2060fe5..9ceb75487f 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,36 +1,43 @@
module ActiveModel
module Validations
class LengthValidator < EachValidator
- OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
- attr_reader :type
def initialize(options)
- @type = (OPTIONS & options.keys).first
+ if range = (options.delete(:in) || options.delete(:within))
+ raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
+ options[:minimum], options[:maximum] = range.begin, range.end
+ options[:maximum] -= 1 if range.exclude_end?
+ end
+
super(options.reverse_merge(:tokenizer => DEFAULT_TOKENIZER))
end
def check_validity!
- ensure_one_range_option!
- ensure_argument_types!
+ keys = CHECKS.keys & options.keys
+
+ if keys.empty?
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ end
+
+ keys.each do |key|
+ value = options[key]
+
+ unless value.is_a?(Integer) && value >= 0
+ raise ArgumentError, ":#{key} must be a nonnegative Integer"
+ end
+ end
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
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
- checks.each do |key, check_value|
+ CHECKS.each do |key, validity_check|
+ next unless check_value = options[key]
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)
@@ -38,33 +45,8 @@ module ActiveModel
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
-
- protected
-
- def ensure_one_range_option! #:nodoc:
- range_options = OPTIONS & options.keys
-
- case range_options.size
- when 0
- 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.'
- end
- end
-
- def ensure_argument_types! #:nodoc:
- value = options[type]
-
- case type
- when :within, :in
- raise ArgumentError, ":#{type} must be a Range" unless value.is_a?(Range)
- when :is, :minimum, :maximum
- raise ArgumentError, ":#{type} must be a nonnegative Integer" unless value.is_a?(Integer) && value >= 0
+ next if valid_value
+ record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value)
end
end
end
@@ -107,8 +89,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
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index f2aab8c5b8..c6d84c5312 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -10,9 +10,10 @@ module ActiveModel
end
def check_validity!
- options.slice(*CHECKS.keys) do |option, value|
- next if [:odd, :even].include?(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)
+ keys = CHECKS.keys - [:odd, :even]
+ options.slice(*keys).each do |option, value|
+ next if value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
+ raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
end
end
@@ -103,8 +104,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
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index a4c6f866a7..4a71cf79b5 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -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
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
new file mode 100644
index 0000000000..4c82a993ae
--- /dev/null
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -0,0 +1,106 @@
+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.
+ #
+ # class Film
+ # include ActiveModel::Validations
+ #
+ # class TitleValidator < ActiveModel::EachValidator
+ # def validate_each(record, attribute, value)
+ # record.errors[attribute] << "must start with 'the'" unless =~ /^the/i
+ # end
+ # end
+ #
+ # validates :name, :title => true
+ # end
+ #
+ # The validators hash can also handle regular expressions, ranges and arrays:
+ #
+ # 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:
+ #
+ # validates :password, :presence => { :if => :password_required? }, :confirmation => true
+ #
+ # Or to all at the same time:
+ #
+ # validates :password, :presence => true, :confirmation => true, :if => :password_required?
+ #
+ def validates(*attributes)
+ defaults = attributes.extract_options!
+ validations = defaults.slice!(:if, :unless, :on, :allow_blank, :allow_nil)
+
+ 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|
+ begin
+ validator = const_get("#{key.to_s.camelize}Validator")
+ rescue NameError
+ raise ArgumentError, "Unknown validator: '#{key}'"
+ end
+
+ validates_with(validator, defaults.merge(_parse_validates_options(options)))
+ end
+ end
+
+ protected
+
+ def _parse_validates_options(options) #:nodoc:
+ case options
+ when TrueClass
+ {}
+ when Hash
+ options
+ when Regexp
+ { :with => options }
+ when Range, Array
+ { :in => options }
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 8d521173c6..db563876af 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -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 \ No newline at end of file