Introduction
When jobs fail permanently, you need to know. Active Job provides rescue_from and after_discard callbacks for custom error handling.
Key Concepts
- rescue_from: Catches exceptions for custom handling. Re-raise to allow retries.
- after_discard: Runs after a job is permanently discarded.
Real World Context
A payment job that exhausts retries needs immediate attention. An import that fails on one record should log and continue.
Deep Dive
rescue_from
rubyclass CriticalJob < ApplicationJob retry_on StandardError, attempts: 3 rescue_from StandardError do |exception| Rails.logger.error "Job failed: #{exception.message}" ErrorTracker.notify(exception) raise exception # Re-raise to trigger retry_on end def perform(data) # Job logic end end
after_discard
rubyclass PaymentJob < ApplicationJob retry_on PaymentGateway::Error, attempts: 5 after_discard do |job, exception| AdminMailer.job_permanently_failed( job_class: job.class.name, arguments: job.arguments, error: exception&.message ).deliver_later(queue: :critical) end def perform(payment_id) Payment.find(payment_id).then { |p| PaymentGateway.charge(p) } end end
Common Pitfalls
- Forgetting to re-raise in rescue_from — Without re-raising, the job is considered successful.
- Sending failure notifications on the same backed-up queue — Use a separate
:criticalqueue.
Best Practices
- Integrate error tracking — Sentry, Honeybadger, etc.
- Update affected records on failure — Set a
failedstatus so dashboards show what happened.
Summary
rescue_fromhandles exceptions — re-raise to allow retries.after_discardruns when all retries are exhausted.- Integrate error tracking for automated alerting.
- Update records to reflect failure status.
Code Examples
ruby
class ImportJob < ApplicationJob
retry_on StandardError, attempts: 3
after_discard do |job, exception|
import = Import.find(job.arguments.first)
import.update!(status: :failed, error: exception&.message)
AdminNotifier.alert("Import #{import.id} permanently failed")
end
def perform(import_id)
Import.find(import_id).then { |i| ImportService.process(i) }
end
end