| | 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 |
|---|