When a blog is created, then we might want to generate a permanent URL for the blog. Similarly, when someone changes their 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.
List of callbacks
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
The before_create, around_create and after_create are replaced by before_update, around_update and after_update respectively when records are updated.
Active Record provides only four callbacks when records are destroyed. They are invoked in the following order.
1before_destroy 2around_destroy 3after_destroy 4after_commit/after_rollback
Active Record callbacks are useful as we can place repetitive behavior in a single place, in turn abiding to the DRY principle.
Registering callbacks
Using callbacks for validations
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.
Open task.rb file and add following lines of code:
1class Task < ApplicationRecord 2 # previous code 3 4 before_validation :set_title 5 validates :title, presence: true, length: { maximum: 50 } 6 7 private 8 9 def set_title 10 self.title = "Pay electricity bill" 11 end 12end
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, task.valid? returned true, 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 after_validation callback:
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
Using callbacks for saving data
Let's modify the Task model to enable before_validation again:
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 rails console:
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 before_save callback. We can observe similar results if we use before_create.
Callbacks like after_create, after_save and after_commit 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 after_save callback. But the value in the database is the one that was set from our before_validation callback.
after_save vs after_commit
There is a slight but important difference between after_save and after_commit callbacks. after_commit is invoked when a database transaction reaches committed state. In case a transaction fails, for instance, while creating a record, after_commit is not invoked by Active Record, but 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 after_save.
Triggering multiple callbacks of the same type
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 Task model:
1class Task < ApplicationRecord 2 # previous code 3 4 before_validation :set_title 5 before_validation :print_set_title 6 7 private 8 9 def set_title 10 self.title = 'Pay electricity bill' 11 end 12 13 def print_set_title 14 puts self.title 15 end 16end 17
Reload Rails console and observe the following:
1>> task = Task.new 2>> task.title 3#=> nil 4 5>> task.valid? 6#=> "Pay electricity bill"
When valid? method is called, set_title is executed first and Pay electricity bill is assigned to the title attribute. Then print_assigned_title is executed and the assigned value is printed.
Conditionally triggering callbacks
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 true. 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 before_validation callback. 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
Understanding how the callbacks are triggered
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