Active Record uses the new_record? instance method to determine whether an object is already in the database or not.
The following methods trigger validations, and will save the object to the database only if the object is valid:
- create
- create!
- save
- save!
- update
- update_attributes
- update_attributes!
The following methods skip validations, and will save the object to the database regardless of its validity. They should be used with caution.
- decrement!
- decrement_counter
- increment!
- increment_counter
- toggle!
- update_all
- update_attribute
- update_counters
Note that save also has the ability to skip validations if passed :validate => false as argument. This technique should be used with caution.
- save(:validate => false)
Validation Helpers
validates_acceptance_of
This validation is very specific to web applications and this ‘acceptance’ does not need to be recorded anywhere in your database (if you don’t have a field for it, the helper will just create a virtual attribute).
validates_acceptance_of can receive an :accept option, which determines the value that will be considered acceptance. It defaults to “1”, but you can change this.
class Person < ActiveRecord::Base
validates_acceptance_of :terms_of_service, :accept => 'yes'
end
validates_associated
class Library < ActiveRecord::Base
has_many :books
validates_associated :books
end
class Person < ActiveRecord::Base
validates_confirmation_of :email
end
validates_confirmation_of
class Person < ActiveRecord::Base
validates_confirmation_of :email
end
<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>
validates_exclusion_of
class Account < ActiveRecord::Base
validates_exclusion_of :subdomain, :in => %w(www),
:message => "Subdomain %{value} is reserved."
end
This example uses the :message option to show how you can include the attribute’s value.
validates_format_of
class Product < ActiveRecord::Base
validates_format_of :legacy_code, :with => /\A[a-zA-Z]+\z/,
:message => "Only letters allowed"
end
validates_inclusion_of
class Coffee < ActiveRecord::Base
validates_inclusion_of :size, :in => %w(small medium large),
:message => "%{value} is not a valid size"
end
validates_length_of
- :minimum – The attribute cannot have less than the specified length.
- :maximum – The attribute cannot have more than the specified length.
- :in (or :within) – The attribute length must be included in a given interval. The value for this option must be a range.
- :is – The attribute length must be equal to the given value.
:wrong_length, :too_long, and :too_short
class Person < ActiveRecord::Base
validates_length_of :bio, :maximum => 1000,
:too_long => "%{count} characters is the maximum allowed"
end
validates_numericality_of
class Player < ActiveRecord::Base
validates_numericality_of :points
validates_numericality_of :games_played, :only_integer => true
end
* :greater_than
* :greater_than_or_equal_to
* :equal_to
* :less_than
* :less_than_or_equal_to
* :odd
* :even
validates_presence_of
If you want to be sure that an association is present, you’ll need to test whether the foreign key used to map the association is present, and not the associated object itself.
class LineItem < ActiveRecord::Base
belongs_to :order
validates_presence_of :order_id
end
Since
false.blank? is true, if you want to validate the presence of a boolean field you should use
validates_inclusion_of :field_name, :in => [true, false].
validates_uniqueness_of
class Holiday < ActiveRecord::Base
validates_uniqueness_of :name, :scope => :year,
:message => "should happen once per year"
end
class Person < ActiveRecord::Base
validates_uniqueness_of :name, :case_sensitive => false
end
validates_with
class Person < ActiveRecord::Base
validates_with GoodnessValidator
end
class GoodnessValidator < ActiveModel::Validator
def validate
if record.first_name == "Evil"
record.errors[:base] << "This person is evil"
end
end
end
The validator class has two attributes by default:
- record – the record to be validated
- options – the extra options that were passed to validates_with
class Person < ActiveRecord::Base
validates_with GoodnessValidator, :fields => [:first_name, :last_name]
end
class GoodnessValidator < ActiveRecord::Validator
def validate
if options[:fields].any?{|field| record.send(field) == "Evil" }
record.errors[:base] << "This person is evil"
end
end
end
validates_each
It doesn’t have a predefined validation function. You should create one using a block, and every attribute passed to validates_each will be tested against it. In the following example, we don’t want names and surnames to begin with lower case.class Person < ActiveRecord::Base
validates_each :name, :surname do |model, attr, value|
model.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/
end
end
Common Validation Options
:allow_nil
class Coffee < ActiveRecord::Base
validates_inclusion_of :size, :in => %w(small medium large),
:message => "%{value} is not a valid size", :allow_nil => true
end
:allow_blank
:message
:on
The default behavior for all the built-in validation helpers is to be run on save (both when you’re creating a new record and when you’re updating it)Conditional Validation
Using a Symbol with :if and :unless
class Order < ActiveRecord::Base
validates_presence_of :card_number, :if => :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
Using a String with :if and :unless
You can also use a string that will be evaluated using eval and needs to contain valid Ruby code.class Person < ActiveRecord::Base
validates_presence_of :surname, :if => "name.nil?"
end
Using a Proc with :if and :unless
class Account < ActiveRecord::Base
validates_confirmation_of :password,
:unless => Proc.new { |a| a.password.blank? }
end
Creating Custom Validation Methods
You must then register these methods by using one or more of the validate, validate_on_create or validate_on_update class methods, passing in the symbols for the validation methods’ names.
You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.
class Invoice < ActiveRecord::Base
validate :expiration_date_cannot_be_in_the_past,
:discount_cannot_be_greater_than_total_value
def expiration_date_cannot_be_in_the_past
errors.add(:expiration_date, "can't be in the past") if
!expiration_date.blank? and expiration_date < Date.today
end
def discount_cannot_be_greater_than_total_value
errors.add(:discount, "can't be greater than total value") if
discount > total_value
end
end
You can even create your own validation helpers and reuse them in several different models.
ActiveRecord::Base.class_eval do
def self.validates_as_choice(attr_name, n, options={})
validates_inclusion_of attr_name, {:in => 1..n}.merge(options)
end
end
Simply reopen ActiveRecord::Base and define a class method like that. You’d typically put this code somewhere in config/initializers. You can use this helper like this:
class Movie < ActiveRecord::Base
validates_as_choice :rating, 5
end
Working with Validation Errors
Of course, calling errors.clear upon an invalid object won’t actually make it valid: the errors collection will now be empty, but the next time you call valid? or any method that tries to save this object to the database, the validations will run again. If any of the validations fail, the errors collection will be filled again.
<%= form_for(@product) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :description %><br />
<%= f.text_field :description %>
</p>
<p>
<%= f.label :value %><br />
<%= f.text_field :value %>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
<%= error_messages_for :product %>
<%= f.error_messages :header_message => "Invalid product!",
:message => "You'll need to fix the following fields:",
:header_tag => :h3 %>
- .field_with_errors – Style for the form fields and labels with errors.
- #errorExplanation – Style for the div element with the error messages.
- #errorExplanation h2 – Style for the header of the div element.
- #errorExplanation p – Style for the paragraph that holds the message that appears right below the header of the div element.
- #errorExplanation ul li – Style for the list items with individual error messages.
Customizing the Error Messages HTML
The way form fields with errors are treated is defined by ActionView::Base.field_error_proc. This is a Proc that receives two parameters:- A string with the HTML tag
- An instance of ActionView::Helpers::InstanceTag.
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
if instance.error_message.kind_of?(Array)
%(#{html_tag}<span class="validation-error">
#{instance.error_message.join(',')}</span>).html_safe
else
%(#{html_tag}<span class="validation-error">
#{instance.error_message}</span>).html_safe
end
end
Callback Registration
class User < ActiveRecord::Base
validates_presence_of :login, :email
before_validation :ensure_login_has_a_value
protected
def ensure_login_has_a_value
if login.nil?
self.login = email unless email.blank?
end
end
end
class User < ActiveRecord::Base
validates_presence_of :login, :email
before_create {|user| user.name = user.login.capitalize
if user.name.blank?}
end
10 Available Callbacks
10.1 Creating an Object
- before_validation
- after_validation
- before_save
- after_save
- before_create
- around_create
- after_create
10.2 Updating an Object
- before_validation
- after_validation
- before_save
- after_save
- before_update
- around_update
- after_update
10.3 Destroying an Object
- before_destroy
- after_destroy
- around_destroy
after_initialize can be useful to avoid the need to directly override your Active Record initialize method.
The after_find callback will be called whenever Active Record loads a record from the database. after_find is called before after_initialize if both are defined.
They have no before_* counterparts, and the only way to register them is by defining them as regular methods. If you try to register after_initialize or after_find using macro-style class methods, they will just be ignored. This behaviour is due to performance reasons, since after_initialize and after_find will both be called for each record found in the database, significantly slowing down the queries.
class User < ActiveRecord::Base
def after_initialize
puts "You have initialized an object!"
end
def after_find
puts "You have found an object!"
end
end
>> User.new
You have initialized an object!
=> #<User id: nil>
>> User.first
You have found an object!
You have initialized an object!
=> #<User id: 1>
Running Callbacks
- create
- create!
- decrement!
- destroy
- destroy_all
- increment!
- save
- save!
- save(false)
- toggle!
- update
- update_attribute
- update_attributes
- update_attributes!
- valid?
Additionally, the after_find callback is triggered by the following finder methods:
- all
- first
- find
- find_all_by_attribute
- find_by_attribute
- find_by_attribute!
- last
The after_initialize callback is triggered every time a new object of the class is initialized.
Skipping Callbacks
- decrement
- decrement_counter
- delete
- delete_all
- find_by_sql
- increment
- increment_counter
- toggle
- update_all
- update_counters
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
class Post < ActiveRecord::Base
after_destroy :log_destroy_action
def log_destroy_action
puts 'Post destroyed'
end
end
>> user = User.first
=> #<User id: 1>
>> user.posts.create!
=> #<Post id: 1, user_id: 1>
>> user.destroy
Post destroyed
=> #<User id: 1>
Conditional Callbacks
the same to validation.
Callback Classes
class PictureFileCallbacks
def after_destroy(picture_file)
File.delete(picture_file.filepath)
if File.exists?(picture_file.filepath)
end
end
class PictureFile < ActiveRecord::Base
after_destroy PictureFileCallbacks.new
end
class PictureFileCallbacks
def self.after_destroy(picture_file)
File.delete(picture_file.filepath)
if File.exists?(picture_file.filepath)
end
end
class PictureFile < ActiveRecord::Base
after_destroy PictureFileCallbacks
end
You can declare as many callbacks as you want inside your callback classes.
Observers
rails generate observer User
class UserObserver < ActiveRecord::Observer
def after_create(model)
# code to send confirmation email...
end
end
Observers are conventionally placed inside of your app/models directory and registered in your application’s config/application.rb file. For example, the UserObserver above would be saved as app/models/user_observer.rb and registered in config/application.rb this way:
# Activate observers that should always be running
config.active_record.observers = :user_observer
As usual, settings in config/environments take precedence over those in config/application.rb. So, if you prefer that an observer doesn’t run in all environments, you can simply register it in a specific environment instead.
sharing observers
class MailerObserver < ActiveRecord::Observer
observe :registration, :user
def after_create(model)
# code to send confirmation email...
end
end