Callbacks in Ruby on Rails: Full Tutorial & Use Cases

Callbacks in Rails: Easy Guide with Examples, Real Use Cases
๐Ÿงฉ Callbacks in Rails โ€“ Full Guide with Examples & 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 checks
  • before_save โ€” runs before a record is created or updated
  • after_create โ€” runs after a record is successfully created
  • after_commit โ€” runs only after a successful database transaction
  • before_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

TermDescription
before_validationRuns before validations are performed on the object
after_validationRuns after validations are completed
before_saveRuns before a record is created or updated
after_saveRuns after a record is created or updated
before_createRuns only before a new record is inserted into the database
after_createRuns only after a new record has been inserted
before_updateRuns before a record is updated
after_updateRuns after a record is updated
before_destroyRuns before a record is deleted
after_destroyRuns after a record has been deleted
after_commitRuns after the database transaction is committed
around_saveWraps logic before and after saving using yield
skip_callbackUsed to disable a specific callback
touchUpdates 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

  1. before_validation
  2. after_validation
  3. before_save
  4. before_create
  5. Record is inserted into DB
  6. after_create
  7. after_save
  8. after_commit

๐Ÿ” Order During Update

  1. before_validation
  2. after_validation
  3. before_save
  4. before_update
  5. Record is updated in DB
  6. after_update
  7. after_save
  8. after_commit

โŒ Order During Destroy

  1. before_destroy
  2. Record is deleted
  3. after_destroy
  4. 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
CallbackWhen It ExecutesExample Action
before_validationRight before validations are checkedAuto-fill missing username
after_validationAfter validations are doneFix casing for email
before_saveBefore saving (both create and update)Encrypt the password field
before_createOnly before a new record is insertedSet default role to user
after_createAfter a new record is insertedSend welcome email
before_updateBefore updating an existing recordTrack what fields are changing
after_updateAfter record is updatedSend update notification
before_destroyBefore record is deletedBlock deletion if user is admin
after_destroyAfter record is deletedLog the user deletion event
after_commitAfter DB transaction completesSend 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 Callback

This 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_, or around_ 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 Skipping

This 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: or if: 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 or Rails.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)

QuestionAnswer 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.
before_save do
      throw(:abort) if email.blank?
    end
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.
around_save do |record, block|
      log_start
      block.call
      log_end
    end
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 Callbacks

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

Learn more aboutย Railsย setup

Scroll to Top