Permalink
Browse files

Merge commit 'josevalim/validations'

  • Loading branch information...
2 parents 91e28aa + 74098e4 commit 632df063a33fab68b50ed893630af7f38821878d @jeremy jeremy committed Dec 28, 2009
Showing with 555 additions and 547 deletions.
  1. +4 −3 activemodel/lib/active_model.rb
  2. +22 −4 activemodel/lib/active_model/naming.rb
  3. +1 −21 activemodel/lib/active_model/translation.rb
  4. +23 −33 activemodel/lib/active_model/validations.rb
  5. +14 −7 activemodel/lib/active_model/validations/acceptance.rb
  6. +11 −9 activemodel/lib/active_model/validations/confirmation.rb
  7. +15 −11 activemodel/lib/active_model/validations/exclusion.rb
  8. +15 −13 activemodel/lib/active_model/validations/format.rb
  9. +15 −11 activemodel/lib/active_model/validations/inclusion.rb
  10. +72 −57 activemodel/lib/active_model/validations/length.rb
  11. +64 −58 activemodel/lib/active_model/validations/numericality.rb
  12. +8 −7 activemodel/lib/active_model/validations/presence.rb
  13. +3 −8 activemodel/lib/active_model/validations/with.rb
  14. +48 −7 activemodel/lib/active_model/validator.rb
  15. +2 −1 activemodel/test/cases/naming_test.rb
  16. +15 −21 activemodel/test/cases/translation_test.rb
  17. +12 −21 activemodel/test/cases/validations/acceptance_validation_test.rb
  18. +3 −2 activemodel/test/cases/validations/conditional_validation_test.rb
  19. +12 −22 activemodel/test/cases/validations/confirmation_validation_test.rb
  20. +12 −11 activemodel/test/cases/validations/exclusion_validation_test.rb
  21. +12 −21 activemodel/test/cases/validations/format_validation_test.rb
  22. +0 −1 activemodel/test/cases/validations/i18n_validation_test.rb
  23. +12 −21 activemodel/test/cases/validations/inclusion_validation_test.rb
  24. +12 −41 activemodel/test/cases/validations/length_validation_test.rb
  25. +12 −29 activemodel/test/cases/validations/numericality_validation_test.rb
  26. +26 −28 activemodel/test/cases/validations/presence_validation_test.rb
  27. +10 −9 activemodel/test/cases/validations/with_validation_test.rb
  28. +3 −2 activemodel/test/cases/validations_test.rb
  29. +5 −1 activemodel/test/models/person.rb
  30. +4 −0 activemodel/test/models/track_back.rb
  31. +9 −7 activerecord/lib/active_record/validations/associated.rb
  32. +76 −58 activerecord/lib/active_record/validations/uniqueness.rb
  33. +2 −1 activerecord/test/cases/helper.rb
  34. +1 −1 {activemodel/lib/active_model → activerecord/test/cases}/validations_repair_helper.rb
@@ -35,16 +35,17 @@ 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
autoload :Translation
autoload :Validations
- autoload :ValidationsRepairHelper
autoload :Validator
+ autoload :EachValidator, 'active_model/validator'
+ autoload :BlockValidator, 'active_model/validator'
autoload :VERSION
module Serializers
@@ -2,11 +2,11 @@
module ActiveModel
class Name < String
- attr_reader :singular, :plural, :element, :collection, :partial_path, :human
+ attr_reader :singular, :plural, :element, :collection, :partial_path
alias_method :cache_key, :collection
- def initialize(klass, name)
- super(name)
+ def initialize(klass)
+ super(klass.name)
@klass = klass
@singular = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze
@plural = ActiveSupport::Inflector.pluralize(@singular).freeze
@@ -15,13 +15,31 @@ def initialize(klass, name)
@collection = ActiveSupport::Inflector.tableize(self).freeze
@partial_path = "#{@collection}/#{@element}".freeze
end
+
+ # Transform the model name into a more humane format, using I18n. By default,
+ # it will underscore then humanize the class name (BlogPost.model_name.human #=> "Blog post").
+ # Specify +options+ with additional translating options.
+ def human(options={})
+ return @human unless @klass.respond_to?(:lookup_ancestors) &&
+ @klass.respond_to?(:i18n_scope)
+
+ defaults = @klass.lookup_ancestors.map do |klass|
+ klass.model_name.underscore.to_sym
+ end
+
+ defaults << options.delete(:default) if options[:default]
+ defaults << @human
+
+ options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults
+ I18n.translate(defaults.shift, options)
+ end
end
module Naming
# Returns an ActiveModel::Name object for module. It can be
# used to retrieve all kinds of naming-related information.
def model_name
- @_model_name ||= ActiveModel::Name.new(self, name)
+ @_model_name ||= ActiveModel::Name.new(self)
end
end
end
@@ -37,28 +37,8 @@ def human_attribute_name(attribute, options = {})
# Model.human_name is deprecated. Use Model.model_name.human instead.
def human_name(*args)
- ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,1])
+ ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,5])
model_name.human(*args)
end
end
-
- class Name < String
- # Transform the model name into a more humane format, using I18n. By default,
- # it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post").
- # Specify +options+ with additional translating options.
- def human(options={})
- return @human unless @klass.respond_to?(:lookup_ancestors) &&
- @klass.respond_to?(:i18n_scope)
-
- defaults = @klass.lookup_ancestors.map do |klass|
- klass.model_name.underscore.to_sym
- end
-
- defaults << options.delete(:default) if options[:default]
- defaults << @human
-
- options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults
- I18n.translate(defaults.shift, options)
- end
- end
end
@@ -13,6 +13,29 @@ module Validations
end
module ClassMethods
+ # Validates each attribute against a block.
+ #
+ # class Person < ActiveRecord::Base
+ # 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>: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
+ # 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
+ # 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
+ validates_with BlockValidator, options.merge(:attributes => attr_names.flatten), &block
+ end
+
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldly and
# you're looking for more descriptive declaration of your validations.
@@ -40,39 +63,6 @@ module ClassMethods
# end
#
# This usage applies to +validate_on_create+ and +validate_on_update as well+.
-
- # Validates each attribute against a block.
- #
- # class Person < ActiveRecord::Base
- # 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>: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
- # 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
- # method, proc or string should return or evaluate to a true or false value.
- def validates_each(*attrs)
- options = attrs.extract_options!.symbolize_keys
- attrs = attrs.flatten
-
- # Declare the validation.
- validate options do |record|
- attrs.each do |attr|
- value = record.send(:read_attribute_for_validation, attr)
- next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
- yield record, attr, value
- end
- end
- end
-
def validate(*args, &block)
options = args.last
if options.is_a?(Hash) && options.key?(:on)
@@ -1,5 +1,17 @@
module ActiveModel
module Validations
+ class AcceptanceValidator < EachValidator
+ def initialize(options)
+ super(options.reverse_merge(:allow_nil => true, :accept => "1"))
+ end
+
+ 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 +37,7 @@ module ClassMethods
# 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 = attr_names.extract_options!
db_cols = begin
column_names
@@ -37,11 +48,7 @@ def validates_acceptance_of(*attr_names)
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
@@ -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 ClassMethods
# 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
@@ -1,5 +1,17 @@
module ActiveModel
module Validations
+ class ExclusionValidator < EachValidator
+ def check_validity!
+ 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
+
+ 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 +33,9 @@ module ClassMethods
# 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]
-
- 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?)
-
- 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
+ options = attr_names.extract_options!
+ options[:in] ||= options.delete(:within)
+ validates_with ExclusionValidator, options.merge(:attributes => attr_names)
end
end
end
@@ -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 ClassMethods
# 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
@@ -1,5 +1,17 @@
module ActiveModel
module Validations
+ class InclusionValidator < EachValidator
+ def check_validity!
+ 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
+
+ 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 +33,9 @@ module ClassMethods
# 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]
-
- 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?)
-
- 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
+ options = attr_names.extract_options!
+ options[:in] ||= options.delete(:within)
+ validates_with InclusionValidator, options.merge(:attributes => attr_names)
end
end
end
Oops, something went wrong.

0 comments on commit 632df06

Please sign in to comment.