Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 9171

Show
Ignore:
Timestamp:
03/31/08 23:40:34 (1 month ago)
Author:
david
Message:

Something more to work with

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activemodel/lib/active_model/validations.rb

    r8118 r9171  
    11module ActiveModel 
    22  module Validations 
     3    VALIDATIONS = %w( validate validate_on_create validate_on_update ) 
     4 
     5    def self.included(base) # :nodoc: 
     6      base.extend(ClassMethods) 
     7      base.send!(:include, ActiveSupport::Callbacks) 
     8 
     9      VALIDATIONS.each do |validation_method| 
     10        base.class_eval <<-"end_eval" 
     11          def self.#{validation_method}(*methods, &block) 
     12            methods = CallbackChain.build(:#{validation_method}, *methods, &block) 
     13            self.#{validation_method}_callback_chain.replace(#{validation_method}_callback_chain | methods) 
     14          end 
     15 
     16          def self.#{validation_method}_callback_chain 
     17            if chain = read_inheritable_attribute(:#{validation_method}) 
     18              return chain 
     19            else 
     20              write_inheritable_attribute(:#{validation_method}, CallbackChain.new) 
     21              return #{validation_method}_callback_chain 
     22            end 
     23          end 
     24        end_eval 
     25      end 
     26    end 
     27 
     28    # All of the following validations are defined in the class scope of the model that you're interested in validating. 
     29    # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use 
     30    # these over the low-level calls to validate and validate_on_create when possible. 
     31    module ClassMethods 
     32      DEFAULT_VALIDATION_OPTIONS = { 
     33        :on => :save, 
     34        :allow_nil => false, 
     35        :allow_blank => false, 
     36        :message => nil 
     37      }.freeze 
     38 
     39      ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze 
     40      ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=', 
     41                                  :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=', 
     42                                  :odd => 'odd?', :even => 'even?' }.freeze 
     43 
     44      # Adds a validation method or block to the class. This is useful when 
     45      # overriding the #validate instance method becomes too unwieldly and 
     46      # you're looking for more descriptive declaration of your validations. 
     47      # 
     48      # This can be done with a symbol pointing to a method: 
     49      # 
     50      #   class Comment < ActiveRecord::Base 
     51      #     validate :must_be_friends 
     52      # 
     53      #     def must_be_friends 
     54      #       errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee) 
     55      #     end 
     56      #   end 
     57      # 
     58      # Or with a block which is passed the current record to be validated: 
     59      # 
     60      #   class Comment < ActiveRecord::Base 
     61      #     validate do |comment| 
     62      #       comment.must_be_friends 
     63      #     end 
     64      # 
     65      #     def must_be_friends 
     66      #       errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee) 
     67      #     end 
     68      #   end 
     69      # 
     70      # This usage applies to #validate_on_create and #validate_on_update as well. 
     71 
     72      # Validates each attribute against a block. 
     73      # 
     74      #   class Person < ActiveRecord::Base 
     75      #     validates_each :first_name, :last_name do |record, attr, value| 
     76      #       record.errors.add attr, 'starts with z.' if value[0] == ?z 
     77      #     end 
     78      #   end 
     79      # 
     80      # Options: 
     81      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update) 
     82      # * <tt>allow_nil</tt> - Skip validation if attribute is nil. 
     83      # * <tt>allow_blank</tt> - Skip validation if attribute is blank. 
     84      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     85      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     86      #   method, proc or string should return or evaluate to a true or false value. 
     87      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     88      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     89      #   method, proc or string should return or evaluate to a true or false value. 
     90      def validates_each(*attrs) 
     91        options = attrs.extract_options!.symbolize_keys 
     92        attrs   = attrs.flatten 
     93 
     94        # Declare the validation. 
     95        send(validation_method(options[:on] || :save), options) do |record| 
     96          attrs.each do |attr| 
     97            value = record.send(attr) 
     98            next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) 
     99            yield record, attr, value 
     100          end 
     101        end 
     102      end 
     103 
     104      # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example: 
     105      # 
     106      #   Model: 
     107      #     class Person < ActiveRecord::Base 
     108      #       validates_confirmation_of :user_name, :password 
     109      #       validates_confirmation_of :email_address, :message => "should match confirmation" 
     110      #     end 
     111      # 
     112      #   View: 
     113      #     <%= password_field "person", "password" %> 
     114      #     <%= password_field "person", "password_confirmation" %> 
     115      # 
     116      # The added +password_confirmation+ attribute is virtual; it exists only as an in-memory attribute for validating the password. 
     117      # To achieve this, the validation adds accessors to the model for the confirmation attribute. NOTE: This check is performed 
     118      # only if +password_confirmation+ is not nil, and by default only on save. To require confirmation, make sure to add a presence 
     119      # check for the confirmation attribute: 
     120      # 
     121      #   validates_presence_of :password_confirmation, :if => :password_changed? 
     122      # 
     123      # Configuration options: 
     124      # * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation") 
     125      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update) 
     126      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     127      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     128      #   method, proc or string should return or evaluate to a true or false value. 
     129      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     130      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     131      #   method, proc or string should return or evaluate to a true or false value.       
     132      def validates_confirmation_of(*attr_names) 
     133        configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save } 
     134        configuration.update(attr_names.extract_options!) 
     135 
     136        attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" })) 
     137 
     138        validates_each(attr_names, configuration) do |record, attr_name, value| 
     139          record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation") 
     140        end 
     141      end 
     142 
     143      # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example: 
     144      # 
     145      #   class Person < ActiveRecord::Base 
     146      #     validates_acceptance_of :terms_of_service 
     147      #     validates_acceptance_of :eula, :message => "must be abided" 
     148      #   end 
     149      # 
     150      # If the database column does not exist, the terms_of_service attribute is entirely virtual. This check is 
     151      # performed only if terms_of_service is not nil and by default on save. 
     152      # 
     153      # Configuration options: 
     154      # * <tt>message</tt> - A custom error message (default is: "must be accepted") 
     155      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update) 
     156      # * <tt>allow_nil</tt> - Skip validation if attribute is nil. (default is true) 
     157      # * <tt>accept</tt> - Specifies value that is considered accepted.  The default value is a string "1", which 
     158      #   makes it easy to relate to an HTML checkbox. This should be set to 'true' if you are validating a database 
     159      #   column, since the attribute is typecast from "1" to <tt>true</tt> before validation. 
     160      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     161      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     162      #   method, proc or string should return or evaluate to a true or false value. 
     163      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     164      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     165      #   method, proc or string should return or evaluate to a true or false value.       
     166      def validates_acceptance_of(*attr_names) 
     167        configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" } 
     168        configuration.update(attr_names.extract_options!) 
     169 
     170        db_cols = begin 
     171          column_names 
     172        rescue ActiveRecord::StatementInvalid 
     173          [] 
     174        end 
     175        names = attr_names.reject { |name| db_cols.include?(name.to_s) } 
     176        attr_accessor(*names) 
     177 
     178        validates_each(attr_names,configuration) do |record, attr_name, value| 
     179          record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept] 
     180        end 
     181      end 
     182 
     183      # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example: 
     184      # 
     185      #   class Person < ActiveRecord::Base 
     186      #     validates_presence_of :first_name 
     187      #   end 
     188      # 
     189      # The first_name attribute must be in the object and it cannot be blank. 
     190      # 
     191      # If you want to validate the presence of a boolean field (where the real values are true and false), 
     192      # you will want to use validates_inclusion_of :field_name, :in => [true, false] 
     193      # This is due to the way Object#blank? handles boolean values. false.blank? # => true 
     194      # 
     195      # Configuration options: 
     196      # * <tt>message</tt> - A custom error message (default is: "can't be blank") 
     197      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update) 
     198      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     199      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     200      #   method, proc or string should return or evaluate to a true or false value. 
     201      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     202      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     203      #   method, proc or string should return or evaluate to a true or false value. 
     204      # 
     205      def validates_presence_of(*attr_names) 
     206        configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save } 
     207        configuration.update(attr_names.extract_options!) 
     208 
     209        # can't use validates_each here, because it cannot cope with nonexistent attributes, 
     210        # while errors.add_on_empty can 
     211        send(validation_method(configuration[:on]), configuration) do |record| 
     212          record.errors.add_on_blank(attr_names, configuration[:message]) 
     213        end 
     214      end 
     215 
     216      # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time: 
     217      # 
     218      #   class Person < ActiveRecord::Base 
     219      #     validates_length_of :first_name, :maximum=>30 
     220      #     validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind" 
     221      #     validates_length_of :fax, :in => 7..32, :allow_nil => true 
     222      #     validates_length_of :phone, :in => 7..32, :allow_blank => true 
     223      #     validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" 
     224      #     validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character" 
     225      #     validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me." 
     226      #   end 
     227      # 
     228      # Configuration options: 
     229      # * <tt>minimum</tt> - The minimum size of the attribute 
     230      # * <tt>maximum</tt> - The maximum size of the attribute 
     231      # * <tt>is</tt> - The exact size of the attribute 
     232      # * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute 
     233      # * <tt>in</tt> - A synonym(or alias) for :within 
     234      # * <tt>allow_nil</tt> - Attribute may be nil; skip validation. 
     235      # * <tt>allow_blank</tt> - Attribute may be blank; skip validation. 
     236      # 
     237      # * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)") 
     238      # * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)") 
     239      # * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)") 
     240      # * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation.  An alias of the appropriate too_long/too_short/wrong_length message 
     241      # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update) 
     242      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     243      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     244      #   method, proc or string should return or evaluate to a true or false value. 
     245      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     246      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     247      #   method, proc or string should return or evaluate to a true or false value.       
     248      def validates_length_of(*attrs) 
     249        # Merge given options with defaults. 
     250        options = { 
     251          :too_long     => ActiveRecord::Errors.default_error_messages[:too_long], 
     252          :too_short    => ActiveRecord::Errors.default_error_messages[:too_short], 
     253          :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length] 
     254        }.merge(DEFAULT_VALIDATION_OPTIONS) 
     255        options.update(attrs.extract_options!.symbolize_keys) 
     256 
     257        # Ensure that one and only one range option is specified. 
     258        range_options = ALL_RANGE_OPTIONS & options.keys 
     259        case range_options.size 
     260          when 0 
     261            raise ArgumentError, 'Range unspecified.  Specify the :within, :maximum, :minimum, or :is option.' 
     262          when 1 
     263            # Valid number of options; do nothing. 
     264          else 
     265            raise ArgumentError, 'Too many range options specified.  Choose only one.' 
     266        end 
     267 
     268        # Get range option and value. 
     269        option = range_options.first 
     270        option_value = options[range_options.first] 
     271 
     272        case option 
     273          when :within, :in 
     274            raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range) 
     275 
     276            too_short = options[:too_short] % option_value.begin 
     277            too_long  = options[:too_long]  % option_value.end 
     278 
     279            validates_each(attrs, options) do |record, attr, value| 
     280              value = value.split(//) if value.kind_of?(String) 
     281              if value.nil? or value.size < option_value.begin 
     282                record.errors.add(attr, too_short) 
     283              elsif value.size > option_value.end 
     284                record.errors.add(attr, too_long) 
     285              end 
     286            end 
     287          when :is, :minimum, :maximum 
     288            raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0 
     289 
     290            # Declare different validations per option. 
     291            validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" } 
     292            message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long } 
     293 
     294            message = (options[:message] || options[message_options[option]]) % option_value 
     295 
     296            validates_each(attrs, options) do |record, attr, value| 
     297              value = value.split(//) if value.kind_of?(String) 
     298              record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value] 
     299            end 
     300        end 
     301      end 
     302 
     303      alias_method :validates_size_of, :validates_length_of 
     304 
     305 
     306      # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user 
     307      # can be named "davidhh". 
     308      # 
     309      #   class Person < ActiveRecord::Base 
     310      #     validates_uniqueness_of :user_name, :scope => :account_id 
     311      #   end 
     312      # 
     313      # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters.  For example, 
     314      # making sure that a teacher can only be on the schedule once per semester for a particular class. 
     315      # 
     316      #   class TeacherSchedule < ActiveRecord::Base 
     317      #     validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id] 
     318      #   end 
     319      # 
     320      # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified 
     321      # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself. 
     322      # 
     323      # Because this check is performed outside the database there is still a chance that duplicate values 
     324      # will be inserted in two parallel transactions.  To guarantee against this you should create a  
     325      # unique index on the field. See +add_index+ for more information. 
     326      # 
     327      # Configuration options: 
     328      # * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken") 
     329      # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint. 
     330      # * <tt>case_sensitive</tt> - Looks for an exact match.  Ignored by non-text columns (false by default). 
     331      # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false) 
     332      # * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false) 
     333      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     334      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     335      #   method, proc or string should return or evaluate to a true or false value. 
     336      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     337      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     338      #   method, proc or string should return or evaluate to a true or false value. 
     339      def validates_uniqueness_of(*attr_names) 
     340        configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] } 
     341        configuration.update(attr_names.extract_options!) 
     342 
     343        validates_each(attr_names,configuration) do |record, attr_name, value| 
     344          # The check for an existing value should be run from a class that 
     345          # isn't abstract. This means working down from the current class 
     346          # (self), to the first non-abstract class. Since classes don't know 
     347          # their subclasses, we have to build the hierarchy between self and 
     348          # the record's class. 
     349          class_hierarchy = [record.class] 
     350          while class_hierarchy.first != self 
     351            class_hierarchy.insert(0, class_hierarchy.first.superclass) 
     352          end 
     353 
     354          # Now we can work our way down the tree to the first non-abstract 
     355          # class (which has a database table to query from). 
     356          finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? } 
     357 
     358          if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?) 
     359            condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}" 
     360            condition_params = [value] 
     361          else 
     362            # sqlite has case sensitive SELECT query, while MySQL/Postgresql don't. 
     363            # Hence, this is needed only for sqlite. 
     364            condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}" 
     365            condition_params = [value.downcase] 
     366          end 
     367 
     368          if scope = configuration[:scope] 
     369            Array(scope).map do |scope_item| 
     370              scope_value = record.send(scope_item) 
     371              condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}" 
     372              condition_params << scope_value 
     373            end 
     374          end 
     375 
     376          unless record.new_record? 
     377            condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?" 
     378            condition_params << record.send(:id) 
     379          end 
     380 
     381          results = finder_class.with_exclusive_scope do 
     382            connection.select_all( 
     383              construct_finder_sql( 
     384                :select     => "#{attr_name}", 
     385                :from       => "#{finder_class.quoted_table_name}", 
     386                :conditions => [condition_sql, *condition_params] 
     387              ) 
     388            ) 
     389          end 
     390 
     391          unless results.length.zero? 
     392            found = true 
     393 
     394            # As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate 
     395            # column in ruby when case sensitive option 
     396            if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text? 
     397              found = results.any? { |a| a[attr_name.to_s] == value } 
     398            end 
     399 
     400            record.errors.add(attr_name, configuration[:message]) if found 
     401          end 
     402        end 
     403      end 
     404 
     405 
     406      # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression 
     407      # provided. 
     408      # 
     409      #   class Person < ActiveRecord::Base 
     410      #     validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create 
     411      #   end 
     412      # 
     413      # Note: use \A and \Z to match the start and end of the string, ^ and $ match the start/end of a line. 
     414      # 
     415      # A regular expression must be provided or else an exception will be raised. 
     416      # 
     417      # Configuration options: 
     418      # * <tt>message</tt> - A custom error message (default is: "is invalid") 
     419      # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false) 
     420      # * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false) 
     421      # * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!) 
     422      # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update) 
     423      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     424      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     425      #   method, proc or string should return or evaluate to a true or false value. 
     426      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     427      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     428      #   method, proc or string should return or evaluate to a true or false value. 
     429      def validates_format_of(*attr_names) 
     430        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil } 
     431        configuration.update(attr_names.extract_options!) 
     432 
     433        raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp) 
     434 
     435        validates_each(attr_names, configuration) do |record, attr_name, value| 
     436          record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with] 
     437        end 
     438      end 
     439 
     440      # Validates whether the value of the specified attribute is available in a particular enumerable object. 
     441      # 
     442      #   class Person < ActiveRecord::Base 
     443      #     validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!" 
     444      #     validates_inclusion_of :age, :in => 0..99 
     445      #     validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list" 
     446      #   end 
     447      # 
     448      # Configuration options: 
     449      # * <tt>in</tt> - An enumerable object of available items 
     450      # * <tt>message</tt> - Specifies a customer error message (default is: "is not included in the list") 
     451      # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false) 
     452      # * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false) 
     453      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     454      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     455      #   method, proc or string should return or evaluate to a true or false value. 
     456      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     457      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     458      #   method, proc or string should return or evaluate to a true or false value. 
     459      def validates_inclusion_of(*attr_names) 
     460        configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save } 
     461        configuration.update(attr_names.extract_options!) 
     462 
     463        enum = configuration[:in] || configuration[:within] 
     464 
     465        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?") 
     466 
     467        validates_each(attr_names, configuration) do |record, attr_name, value| 
     468          record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value) 
     469        end 
     470      end 
     471 
     472      # Validates that the value of the specified attribute is not in a particular enumerable object. 
     473      # 
     474      #   class Person < ActiveRecord::Base 
     475      #     validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here" 
     476      #     validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60" 
     477      #     validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed" 
     478      #   end 
     479      # 
     480      # Configuration options: 
     481      # * <tt>in</tt> - An enumerable object of items that the value shouldn't be part of 
     482      # * <tt>message</tt> - Specifies a customer error message (default is: "is reserved") 
     483      # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false) 
     484      # * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false) 
     485      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     486      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     487      #   method, proc or string should return or evaluate to a true or false value. 
     488      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     489      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     490      #   method, proc or string should return or evaluate to a true or false value. 
     491      def validates_exclusion_of(*attr_names) 
     492        configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save } 
     493        configuration.update(attr_names.extract_options!) 
     494 
     495        enum = configuration[:in] || configuration[:within] 
     496 
     497        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?") 
     498 
     499        validates_each(attr_names, configuration) do |record, attr_name, value| 
     500          record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value) 
     501        end 
     502      end 
     503 
     504      # Validates whether the associated object or objects are all valid themselves. Works with any kind of association. 
     505      # 
     506      #   class Book < ActiveRecord::Base 
     507      #     has_many :pages 
     508      #     belongs_to :library 
     509      # 
     510      #     validates_associated :pages, :library 
     511      #   end 
     512      # 
     513      # Warning: If, after the above definition, you then wrote: 
     514      # 
     515      #   class Page < ActiveRecord::Base 
     516      #     belongs_to :book 
     517      # 
     518      #     validates_associated :book 
     519      #   end 
     520      # 
     521      # ...this would specify a circular dependency and cause infinite recursion. 
     522      # 
     523      # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association 
     524      # is both present and guaranteed to be valid, you also need to use validates_presence_of. 
     525      # 
     526      # Configuration options: 
     527      # * <tt>message</tt> - A custom error message (default is: "is invalid") 
     528      # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update) 
     529      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     530      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     531      #   method, proc or string should return or evaluate to a true or false value. 
     532      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     533      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     534      #   method, proc or string should return or evaluate to a true or false value. 
     535      def validates_associated(*attr_names) 
     536        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save } 
     537        configuration.update(attr_names.extract_options!) 
     538 
     539        validates_each(attr_names, configuration) do |record, attr_name, value| 
     540          record.errors.add(attr_name, configuration[:message]) unless 
     541            (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v } 
     542        end 
     543      end 
     544 
     545      # Validates whether the value of the specified attribute is numeric by trying to convert it to 
     546      # a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression 
     547      # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>integer</tt> is set to true). 
     548      # 
     549      #   class Person < ActiveRecord::Base 
     550      #     validates_numericality_of :value, :on => :create 
     551      #   end 
     552      # 
     553      # Configuration options: 
     554      # * <tt>message</tt> - A custom error message (default is: "is not a number") 
     555      # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update) 
     556      # * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false) 
     557      # * <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 
     558      # * <tt>greater_than</tt> Specifies the value must be greater than the supplied value 
     559      # * <tt>greater_than_or_equal_to</tt> Specifies the value must be greater than or equal the supplied value 
     560      # * <tt>equal_to</tt> Specifies the value must be equal to the supplied value 
     561      # * <tt>less_than</tt> Specifies the value must be less than the supplied value 
     562      # * <tt>less_than_or_equal_to</tt> Specifies the value must be less than or equal the supplied value 
     563      # * <tt>odd</tt> Specifies the value must be an odd number 
     564      # * <tt>even</tt> Specifies the value must be an even number 
     565      # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should 
     566      #   occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }).  The 
     567      #   method, proc or string should return or evaluate to a true or false value. 
     568      # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should 
     569      #   not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }).  The 
     570      #   method, proc or string should return or evaluate to a true or false value. 
     571      def validates_numericality_of(*attr_names) 
     572        configuration = { :on => :save, :only_integer => false, :allow_nil => false } 
     573        configuration.update(attr_names.extract_options!) 
     574 
     575 
     576        numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys 
     577 
     578        (numericality_options - [ :odd, :even ]).each do |option| 
     579          raise ArgumentError, ":#{option} must be a number" unless configuration[option].is_a?(Numeric) 
     580        end 
     581 
     582        validates_each(attr_names,configuration) do |record, attr_name, value| 
     583          raw_value = record.send("#{attr_name}_before_type_cast") || value 
     584 
     585          next if configuration[:allow_nil] and raw_value.nil? 
     586 
     587          if configuration[:only_integer] 
     588            unless raw_value.to_s =~ /\A[+-]?\d+\Z/ 
     589              record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number]) 
     590              next 
     591            end 
     592            raw_value = raw_value.to_i 
     593          else 
     594           begin 
     595              raw_value = Kernel.Float(raw_value.to_s) 
     596            rescue ArgumentError, TypeError 
     597              record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number]) 
     598              next 
     599            end 
     600          end 
     601 
     602          numericality_options.each do |option| 
     603            case option 
     604              when :odd, :even 
     605                record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[] 
     606              else 
     607                message = configuration[:message] || ActiveRecord::Errors.default_error_messages[option] 
     608                message = message % configuration[option] if configuration[option] 
     609                record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]] 
     610            end 
     611          end 
     612        end 
     613      end 
     614 
     615      private 
     616        def validation_method(on) 
     617          case on 
     618            when :save   then :validate 
     619            when :create then :validate_on_create 
     620            when :update then :validate_on_update 
     621          end 
     622        end 
     623    end 
     624 
     625    # The validation process on save can be skipped by passing false. The regular Base#save method is 
     626    # replaced with this when the validations module is mixed in, which it is by default. 
     627    def save_with_validation(perform_validation = true) 
     628      if perform_validation && valid? || !perform_validation 
     629        save_without_validation 
     630      else 
     631        false 
     632      end 
     633    end 
     634 
     635    # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false 
     636    # if the record is not valid. 
     637    def save_with_validation! 
     638      if valid? 
     639        save_without_validation! 
     640      else 
     641        raise RecordInvalid.new(self) 
     642      end 
     643    end 
     644 
     645    # Updates a single attribute and saves the record without going through the normal validation procedure. 
     646    # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method 
     647    # in Base is replaced with this when the validations module is mixed in, which it is by default. 
     648    def update_attribute_with_validation_skipping(name, value) 
     649      send(name.to_s + '=', value) 
     650      save(false) 
     651    end 
     652 
     653    # Runs validate and validate_on_create or validate_on_update and returns true if no errors were added otherwise false. 
     654    def valid? 
     655      errors.clear 
     656 
     657      run_callbacks(:validate) 
     658      validate 
     659 
     660      if new_record? 
     661        run_callbacks(:validate_on_create) 
     662        validate_on_create 
     663      else 
     664        run_callbacks(:validate_on_update) 
     665        validate_on_update 
     666      end 
     667 
     668      errors.empty? 
     669    end 
     670 
     671    # Returns the Errors object that holds all information about attribute error messages. 
     672    def errors 
     673      @errors ||= Errors.new(self) 
     674    end 
     675 
     676    protected 
     677      # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes. 
     678      def validate #:doc: 
     679      end 
     680 
     681      # Overwrite this method for validation checks used only on creation. 
     682      def validate_on_create #:doc: 
     683      end 
     684 
     685      # Overwrite this method for validation checks used only on updates. 
     686      def validate_on_update # :doc: 
     687      end 
    3688  end 
    4689end