๐ Contents
- What Are Callbacks?
- Detailed Explanation of Callbacks
- Why Use Callbacks?
- Important Terms in Rails Callbacks
- Callback Order & Execution Flow
- When Each Callback Executes (With Examples)
- Custom Callbacks in Rails
- Skipping Callbacks in Rails (With Examples)
- When a Callback Doesnโt Run in Rails
- Interview Questions & Answers (with Examples)
- Alternatives
- Real World Use Case
- Best Practices
๐ What Are Callbacks?
Callbacks in Rails are methods triggered automatically during the lifecycle of an ActiveRecord object. They hook into events like validation, save, create, update, and destroy.
๐ Detailed Explanation of Callbacks
In Ruby on Rails, callbacks are special hooks that allow you to run code automatically at specific points in an ActiveRecord object’s lifecycle. Instead of manually calling logic before or after an operation like save
or destroy
, you can define callback methods that Rails will trigger for you.
For example, if you want to clean up whitespace in a title before saving a blog post, you can use a before_save
callback rather than calling the method manually every time. This helps reduce duplication and ensures consistency across your application.
Callbacks are used in models, and they can be helpful in keeping your models clean and concise โ but they should be used with care. Excessive or complex callback logic can make it hard to understand what your application is doing.
There are many types of callbacks such as:
before_validation
โ runs before validation checksbefore_save
โ runs before a record is created or updatedafter_create
โ runs after a record is successfully createdafter_commit
โ runs only after a successful database transactionbefore_destroy
โ runs before a record is deleted
These callbacks help automate model behavior, making your code cleaner and more maintainable. But always be careful: for anything complex or business-specific, itโs often better to use a service object instead.
๐ก Why Use Callbacks?
- Enforce business logic before or after DB operations
- Send notifications or update external services
- Set or clean attributes automatically
- Reduce code duplication
๐ Important Terms in Rails Callbacks
Term | Description |
---|---|
before_validation | Runs before validations are performed on the object |
after_validation | Runs after validations are completed |
before_save | Runs before a record is created or updated |
after_save | Runs after a record is created or updated |
before_create | Runs only before a new record is inserted into the database |
after_create | Runs only after a new record has been inserted |
before_update | Runs before a record is updated |
after_update | Runs after a record is updated |
before_destroy | Runs before a record is deleted |
after_destroy | Runs after a record has been deleted |
after_commit | Runs after the database transaction is committed |
around_save | Wraps logic before and after saving using yield |
skip_callback | Used to disable a specific callback |
touch | Updates updated_at timestamp manually, often used in callbacks |
โฑ๏ธ Callback Order & Execution Flow
Yes, the order of callbacks matters. Rails executes them in a strict sequence depending on the action being performed โ whether it’s create, update, or destroy. Below is the full flow of callbacks for a record creation:
๐งฌ Order During Create
before_validation
after_validation
before_save
before_create
- Record is inserted into DB
after_create
after_save
after_commit
๐ Order During Update
before_validation
after_validation
before_save
before_update
- Record is updated in DB
after_update
after_save
after_commit
โ Order During Destroy
before_destroy
- Record is deleted
after_destroy
after_commit
๐ around callbacks like around_save
wrap the save logic โ they run before and after save, and must include yield
to proceed with the operation.
โ
Why order matters: If you update a field in before_validation
, it will be validated. But if you update it in after_validation
, it’s already too late to be validated. Always choose the callback stage based on when you need the logic to run.
๐งช When Each Callback Executes (With Examples)
Hereโs a breakdown of common callback types and exactly when they run, using a simple User
model as reference:
class User < ApplicationRecord
before_validation :set_username
after_validation :normalize_email
before_save :encrypt_password
before_create :set_default_role
after_create :send_welcome_email
before_update :log_changes
after_update :notify_user
before_destroy :check_if_admin
after_destroy :log_deletion
after_commit :track_analytics
end
Callback | When It Executes | Example Action |
---|---|---|
before_validation | Right before validations are checked | Auto-fill missing username |
after_validation | After validations are done | Fix casing for email |
before_save | Before saving (both create and update) | Encrypt the password field |
before_create | Only before a new record is inserted | Set default role to user |
after_create | After a new record is inserted | Send welcome email |
before_update | Before updating an existing record | Track what fields are changing |
after_update | After record is updated | Send update notification |
before_destroy | Before record is deleted | Block deletion if user is admin |
after_destroy | After record is deleted | Log the user deletion event |
after_commit | After DB transaction completes | Send analytics data to external service |
๐ These examples clarify exactly what kind of logic fits best at each stage of the model lifecycle.
๐ ๏ธ Custom Callbacks in Rails
While Rails provides built-in callbacks like before_save
or after_create
, you can also define your own custom callbacks using define_model_callbacks
. This is useful when you want to hook into custom events or group reusable lifecycle actions.
๐ Defining a Custom Callback
class Payment < ApplicationRecord
define_model_callbacks :charge
before_charge :log_start
after_charge :log_end
def charge_card
run_callbacks :charge do
puts "๐ณ Charging card..."
# charge logic here
end
end
private
def log_start
puts "๐ Starting charge..."
end
def log_end
puts "โ
Finished charge."
end
end
In this example, we define a new callback flow called :charge
. When charge_card
is called, it runs the before_charge
and after_charge
callbacks around the card charging logic.
๐ Syntax for Custom Callbacks
define_model_callbacks :event_name
before_event_name :method_name
after_event_name :method_name
around_event_name :method_name
- Trigger using
run_callbacks :event_name do ... end
โ Use Cases for Custom Callbacks
- Complex domain workflows (e.g., payment, shipment, verification)
- Shared behavior across modules/services
- Better testability and code separation
๐ง Pro Tip: Avoid overusing custom callbacks unless youโre managing complex workflows โ they can reduce clarity when abused.
๐ซ Skipping Callbacks in Rails (With Examples & Full Explanation)
In some cases โ like data migration, seeding, or performance-critical operations โ you might want to skip callbacks that normally run when saving, updating, or destroying records. Rails provides multiple ways to do this.
๐ 1.
skip_callback
โ Permanently Remove a CallbackThis method removes a previously declared callback from the model. Use this when you know you never want a specific callback to run anymore โ like turning off email notifications in a staging environment.
class User < ApplicationRecord
before_save :normalize_email
skip_callback :save, :before, :normalize_email
end
๐ Breakdown:
- :save โ The callback chain you want to target (save, create, update, etc.)
- :before โ Whether itโs a
before_
,after_
, oraround_
callback - :normalize_email โ The method name to remove from the chain
โ ๏ธ This removes the callback from the model entirely and affects all records. It is not temporary. Avoid using it inside runtime logic or conditionals.
๐ 2. Skip Callbacks During Updates Using
update_column
or update_all
These methods update the database directly and skip validations and callbacks.
user.update_column(:status, "inactive") # Skips callbacks and validations
User.update_all(active: false) # Mass update, no callbacks
โ ๏ธ Be careful: this bypasses all protections โ use only when you're sure the data is safe to update directly.
โ 3. Skip Conditionally Using a Flag (Safe and Reversible)
You can define a condition inside the callback method using an instance variable or method:
before_save :generate_slug, unless: -> { skip_slug? }
def skip_slug?
@skip_slug == true
end
# In controller or service:
user.instance_variable_set(:@skip_slug, true)
โ This is safe and temporary โ perfect for one-time skips or unit testing.
๐จ 4. Using
save(validate: false)
โ Partial SkippingThis skips validations, but not all callbacks. For example:
user.save(validate: false)
This still runs before_save
and after_save
callbacks, so itโs not a full skip.
๐ง Best Practices
- โ
Use
unless:
orif:
with a method or flag for one-off skips - โ ๏ธ Avoid using
skip_callback
dynamically โ it affects the class globally - โก Prefer
update_column
only in scripts, migrations, or trusted internal ops - ๐งช Clearly document any skipped callback logic to avoid silent bugs
Skipping callbacks should always be intentional. Understand the lifecycle and what you're skipping โ especially when dealing with security, encryption, auditing, or notifications.
๐ When a Callback Doesnโt Run in Rails
Sometimes your callbacks might silently fail or never get triggered. Here are the most common reasons โ and how to fix them.
๐ซ 1. Wrong Lifecycle Callback Used
Using before_create
when the record is already persisted will cause the callback to not run.
# This runs only on new records:
before_create :generate_token
โ
Solution: Use before_save
or before_validation
if you want to cover both create and update.
๐ 2. Condition Blocks (if:
/unless:
) Evaluate to False
If a condition returns false, the callback is skipped.
before_save :normalize_name, if: -> { email.present? }
Check the condition at runtime โ maybe it doesn't match as expected.
๐งช 3. Method is Missing or Misspelled
Rails wonโt raise an error if your callback method name is wrong โ it just wonโt run it.
before_save :normalize_names # wrong method name
# But the real method is: def normalize_name; end
๐ 4. Using update_column
, update_all
, or Raw SQL
These skip validations and callbacks.
user.update_column(:status, "active") # โ Skips callbacks
User.update_all(active: false) # โ Skips all lifecycle hooks
๐งผ 5. Callback Method Doesnโt Change the Model
If your method doesnโt modify the object or raise an error, you might think it didnโt run โ but it did.
before_validation :check_something
def check_something
puts "I ran!" # But doesn't affect anything visible
end
๐ 6. Callback Chain is Halted by Another Callback
If an earlier callback uses throw(:abort)
, the rest wonโt run.
before_save :check_permission
def check_permission
throw(:abort) unless admin?
end
๐ 7. Callback Skipped by skip_callback
or Flag
You (or another dev) may have intentionally skipped the callback and forgot.
skip_callback :save, :before, :normalize_email
๐ง Debugging Tips
- โ
Add
puts
orRails.logger.debug
in your callback methods - โ
Check for spelling mistakes or method visibility (
private
methods wonโt work in some callback cases) - โ Use a test to verify the callback is triggered (e.g. expect a change or side effect)
- โ Check parent class and concerns if the callback is included dynamically
If all else fails, simplify the model and try the callback in isolation. Youโll quickly find whether itโs a logic error or a lifecycle issue.
๐ฏ Interview Questions & Answers (with Examples)
Question | Answer with Example |
---|---|
1. What is a callback in Rails? | A callback is a method that gets triggered automatically at certain points in an object's lifecycle (like save, create, update, destroy).before_save :normalize_name |
2. What is the difference between before_save and before_create ? | before_save runs on both create and update.before_create only runs on new records.Example: before_create :generate_token |
3. Can callbacks stop a save or create action? | Yes. If a before_* callback returns false , it halts the operation.
|
4. When should you avoid callbacks? | Avoid when logic is complex or spans multiple models. Use service objects instead for clarity and testability. |
5. What is after_commit used for? | To trigger logic only after a transaction fully succeeds. Best for emails, jobs, external services. after_commit :notify_admin |
6. Whatโs the purpose of around_save ? | To wrap logic before and after saving using yield .
|
7. Can you conditionally run a callback? | Yes, using if: or unless: options.before_save :encrypt, if: -> { password_changed? } |
8. How do you skip a callback? | You can call:skip_callback :save, :before, :your_method Or use conditions to avoid executing logic inside. |
9. Can you test Rails callbacks? | Yes. You can write specs to check effects of callbacks. Example: Check if after_create sends email. |
10. What is a common pitfall of using callbacks? | Callback chains can become hard to track and debug. They may cause unexpected side effects or duplicate logic. Keep them small and use services for complex workflows. |
๐ Alternatives to Callbacks (With Examples)
Callbacks are helpful, but they can make code harder to understand or debug โ especially when logic grows complex. In such cases, it's better to use alternatives like service objects, observers, or concerns for better code organization.
1. โ Service Objects (Best Practice)
Encapsulate logic into a dedicated class. Easy to test and reuse.
# app/services/user_creator.rb
class UserCreator
def initialize(params)
@params = params
end
def call
user = User.new(@params)
user.username = user.username.downcase
user.save!
SendWelcomeEmailJob.perform_later(user.id)
user
end
end
Usage:
UserCreator.new(params).call
2. ๐ก ActiveRecord Observers
Observers allow logic to live outside the model. They listen to lifecycle events like create, update, or destroy.
# app/observers/user_observer.rb
class UserObserver < ActiveRecord::Observer
def after_create(user)
Analytics.track("user_created", user_id: user.id)
end
end
Enable it in config:
# config/application.rb
config.active_record.observers = :user_observer
3. ๐ฆ Concerns (Shared Logic)
Use Rails concerns to share behavior across models. Avoid callback abuse.
# app/models/concerns/slug_generator.rb
module SlugGenerator
extend ActiveSupport::Concern
included do
before_save :generate_slug
end
def generate_slug
self.slug = name.parameterize
end
end
# In your model:
class Article < ApplicationRecord
include SlugGenerator
end
4. ๐งช Plain Method Calls
Just call the method explicitly in your controller or service. This is the most transparent and beginner-friendly way.
def create
@user = User.new(user_params)
@user.prepare_data
if @user.save
SendWelcomeEmailJob.perform_later(@user.id)
end
end
๐ง When to Use Alternatives
- When logic involves multiple models or side effects
- When you want better testability and traceability
- When callbacks become hard to debug or override
โ ๏ธ Rule of Thumb: Use callbacks for light, model-only logic. For business processes, move to service objects or observers.
๐ข Real World Use Case
In a marketplace app, after a user creates a listing, we:
- Auto-generate a slug with
before_validation
- Send notification to admins with
after_create
- Schedule indexing job with
after_commit
class Listing < ApplicationRecord
before_validation :generate_slug
after_create :notify_admin
after_commit :index_listing, on: :create
end
โ Best Practices for Callbacks in Rails
Rails callbacks can make your code cleaner and more powerful โ but only if used wisely. Follow these best practices to avoid common pitfalls:
๐น 1. Keep Callbacks Lightweight
Callbacks should contain minimal, model-related logic only. Avoid business logic, API calls, or complex flow inside callbacks.
# โ
Good
before_save :normalize_name
# โ Bad
before_save :send_invoice_to_customer
๐น 2. Use Service Objects for Business Logic
Extract logic into service objects when it involves more than a few steps or affects other models.
UserOnboardingService.new(user).call
๐น 3. Use
if:
and unless:
for Conditional CallbacksAvoid running callbacks unnecessarily. Add conditionals to limit execution.
before_save :encrypt_password, if: -> { password_changed? }
๐น 4. Prefer Explicit Over Implicit Behavior
If a developer reading your model canโt tell whatโs happening just by scanning the code, itโs a red flag.
# โ
Controller or Service explicitly calls logic
user.generate_slug
๐น 5. Avoid Chaining Too Many Callbacks
Chained callbacks can lead to complex, unpredictable flows. Keep them clear and few.
๐น 6. Use
after_commit
for External Effects For things like emails, jobs, or analytics, always prefer after_commit
to avoid triggering them if the transaction fails.
๐น 7. Donโt Depend on Callback Order Too Much
If two callbacks must run in a certain order, combine them or document the order explicitly to avoid confusion.
๐น 8. Test Side Effects of Callbacks
Donโt assume callbacks just โwork.โ Write unit tests that check whether the expected outcome actually happens.
๐น 9. Avoid Callbacks in Concerns Unless Shared
Keep concerns clean โ only include callbacks if shared across multiple models.
๐น 10. Comment Complex Callback Chains
If you must use multiple callbacks, document their purpose and order to help future developers.
๐ง Golden Rule: If it feels like magic, itโs probably not a good callback use case. Keep it simple, testable, and predictable.
๐ External Resources to Learn More
If you want to go deeper into callbacks in Rails, here are some official and high-quality community resources worth checking out:
- ๐ Rails Guides: Active Record Callbacks (Official)
The most comprehensive and up-to-date source on all callback types and usage patterns in Rails. - ๐ Learn Ruby Online
Interactive Ruby learning environment to practice lifecycle and callback concepts from scratch. - ๐ฆ ActiveRecord Import Gem
A gem for importing large datasets while skipping callbacks and validations by default.
Learn more aboutย Railsย setup