When a blog is created, then we might want to generate a permanent URL for the blog. Similarly, when someone changes one's password, then we might want to send an email to the person that the password has been changed.
We can achieve such business cases by registering some methods that should be executed by Active Record whenever a model changes.
Active Record Callbacks are hooks to which we can register methods in our models. These hooks are executed in various stages of an Active Record object lifecycle. The following callbacks are run when an object is created. These callbacks are executed in the order in which they are listed below.
1before_validation 2after_validation 3before_save 4around_save 5before_create 6around_create 7after_create 8after_save 9after_commit/after_rollback
are replaced by
after_update respectively when records are updated.
Active Record provides only four callbacks when records are
They are invoked in the following order.
1before_destroy 2around_destroy 3after_destroy 4after_commit/after_rollback
In the previous chapter, we had introduced a validation to ensure that task has a title.
Let's try and modify the value of the
title attribute through callbacks and observe the behavior.
task.rb file and add following lines of code.
1class Task < ApplicationRecord 2 9end
Now open Rails console and execute the following code.
1$ bundle exec rails console 2>> task = Task.new 3>> task.valid? 4#=> true
As seen above,
which means the title was set before the validations were run.
Let's verify if the value was actually set on the record.
1>> task.title 2#=> "Pay electricity bill"
Yay! The task record has
title set by the callback.
Now comment out the line where callback is registered, and register the same method in an
1# before_validation :set_title 2after_validation :set_title
Open Rails console and check the following.
1>> task = Task.new 2>> task.title 3#=> nil 4 5>> task.valid? 6#=> false 7 8>> task.title 9#=> "Pay electricity bill"
Interesting isn't it! The validation has failed and returned
false, but we still have a title assigned.
That's because the attribute was assigned after the validations were run.
As you would expect,
task.save! should fail here.
1task.save! 2#=> ActiveRecord::RecordInvalid: Validation failed: Title enter a value for Title
Let's modify the
Task model to enable
1before_validation :set_title 2 3# Comment out after_validation callback 4# after_validation :set_title
Let's add a
before_save callback to change the title.
Let's also ensure that we also have our
before_validation callback that was enabled above as well.
1class Task < ApplicationRecord 2 # ... existing validations and callbacks 3 4 before_save :change_title 5 6 def change_title 7 self.title = "Pay electricity & TV bill" 8 end 9 10end
Time to try things using
1>> task = Task.new 2>> task.valid? 3#=> true 4 5>> task.title 6#=> "Pay electricity bill" 7 8>> task.save! 9>> task.title 10#=> "Pay electricity & TV bill"
Let's look at the value stored in the database.
1>> task = Task.order(created_at: :desc).first 2>> task.title 3#=> "Pay electricity & TV bill"
As we can see, the value stored in the database is what we have set from
We can observe similar results if we use
will have a different behaviour in above scenarios.
Comment out existing
before_save callback and enable
after_save for same methods.
1# before_save :change_title 2after_save :change_title
Observe results in Rails console.
1>> task = Task.create! 2>> task.title 3#=> "Pay electricity & TV bill" 4 5>> task = Task.last 6>> task.title 7#=> "Pay electricity"
The task object returned from
Task.create! has title updated from the
But the value in the database is the one that was set from our
There is a slight but important difference between
after_commit is invoked when a database transaction reaches
In case a transaction fails, for instance, while creating a record,
after_commit is not invoked by ActiveRecord,
after_save will have run by then.
If there is a requirement that necessitates data to be persisted in the database
before executing a piece of code,
we should use
after_commit instead of
We can register multiple methods for a callback.
They will be chained up and executed in the same order in which they are registered.
Let's have two
before_validation callbacks in our
1class Task < ApplicationRecord 2 3 before_validation :set_title 4 before_validation :print_set_title 5 6 def set_title 7 self.title = 'Pay electricity bill' 8 end 9 10 def print_set_title 11 puts self.title 12 end 13end 14
Reload Rails console and observe the following.
1>> task = Task.new 2>> task.title 3#=> nil 4 5>> task.valid? 6#=> "Pay electricity bill"
valid? method is called,
set_title is executed first and
Pay electricity bill is assigned to the
print_assigned_title is executed and the assigned value is printed.
Let's modify our
before_validation callback as follows.
1before_validation :set_title, if: :title_not_present 2 3def title_not_present 4 self.title.blank? 5end 6 7def set_title 8 self.title = 'Pay electricity bill' 9end
Here we make sure that the
title is assigned a value only when
title_not_present method returns
Let's verify this behavior in Rails console.
1>> task = Task.new(title: "This is a sample task") 2>> task.valid? 3#=> true 4 5>> task.title 6#=> "This is a sample task"
As we can see, title is not set from the
Similarly, we can use
unless: to conditionally trigger a callback when a negative condition is provided.
1before_validation :assign_title, unless: :title_present 2 3def title_present 4 self.title.present? 5end
The following methods, when called on an Active Record object, trigger the above-described callbacks.
1create 2create! 3destroy 4destroy! 5destroy_all 6save 7save! 8save(validate: false) 9toggle! 10touch 11update_attribute 12update 13update! 14valid?
save(validate: false) runs all callbacks as that of
save, except validate callbacks.
Callbacks are NOT triggered when the following methods are called.
1decrement 2decrement_counter 3delete 4delete_all 5increment 6increment_counter 7toggle 8update_column 9update_columns 10update_all 11update_counters
Except the title presence validation we don't need anything else to be committed in this chapter:
1git clean -fd