| name | active-job |
| description | This skill should be used when the user asks about "background jobs", "Active Job", "perform_later", "perform_now", "queue adapters", "job retries", "job callbacks", "async tasks", "scheduled jobs", "job queues", or needs guidance on background processing in Rails applications. |
| version | 1.0.0 |
Active Job
Comprehensive guide to background job processing in Rails with Active Job.
Creating Jobs
rails generate job ProcessOrder
class ProcessOrderJob < ApplicationJob
queue_as :default
def perform(order)
order.process!
OrderMailer.confirmation(order).deliver_now
end
end
Enqueueing Jobs
Immediate Enqueue
ProcessOrderJob.perform_later(order)
ActiveJob.perform_all_later(
ProcessOrderJob.new(order1),
ProcessOrderJob.new(order2)
)
Scheduled Execution
ProcessOrderJob.set(wait_until: Date.tomorrow.noon).perform_later(order)
ProcessOrderJob.set(wait: 1.hour).perform_later(order)
ProcessOrderJob.set(wait: 5.minutes).perform_later(order)
ProcessOrderJob.perform_now(order)
Queue Selection
class ProcessOrderJob < ApplicationJob
queue_as :high_priority
end
ProcessOrderJob.set(queue: :urgent).perform_later(order)
Priority
class LowPriorityJob < ApplicationJob
queue_as :default
queue_with_priority 50
end
ImportantJob.set(priority: 10).perform_later(data)
Queue Adapters
Configuration
config.active_job.queue_adapter = :async
Common Adapters
| Adapter | Best For | Notes |
|---|
:async | Development | In-process, not for production |
:inline | Testing | Runs immediately |
:test | Testing | Stores jobs for assertions |
Retries and Error Handling
Automatic Retries
class ExternalApiJob < ApplicationJob
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
retry_on CustomTransientError, wait: 5.seconds, attempts: 3
def perform(record)
ExternalApi.sync(record)
end
end
Retry Options
| Option | Description |
|---|
wait | Seconds to wait (:polynomially_longer for backoff) |
attempts | Number of retry attempts |
queue | Queue for retried job |
priority | Priority for retried job |
Discard on Error
class DataImportJob < ApplicationJob
discard_on ActiveJob::DeserializationError
discard_on CustomPermanentError
def perform(record_id)
record = Record.find(record_id)
end
end
Custom Error Handling
class ProcessOrderJob < ApplicationJob
rescue_from(PaymentError) do |exception|
order = arguments.first
order.mark_payment_failed!(exception.message)
OrderMailer.payment_failed(order).deliver_later
end
def perform(order)
PaymentService.charge(order)
end
end
Callbacks
class ImportJob < ApplicationJob
before_enqueue :log_enqueue
after_enqueue :notify_enqueued
before_perform :setup
around_perform :measure_time
after_perform :cleanup
after_discard :log_discard
private
def log_enqueue
Rails.logger.info "Enqueueing import job"
end
def setup
@start_time = Time.current
end
def measure_time
start = Time.current
yield
duration = Time.current - start
Rails.logger.info "Job completed in #{duration}s"
end
def cleanup
end
def log_discard(job, exception)
Rails.logger.error "Job discarded: #{exception.message}"
end
end
Job Arguments
Supported Types
Active Job automatically serializes:
- Basic types (String, Integer, Float, NilClass, TrueClass, FalseClass, BigDecimal)
- Symbol (as String)
- Date, Time, DateTime
- ActiveSupport::TimeWithZone
- ActiveSupport::Duration
- Hash (keys as strings)
- ActiveSupport::HashWithIndifferentAccess
- Array
- Range
- Module/Class
- ActiveRecord objects (via GlobalID)
GlobalID Serialization
class NotifyUserJob < ApplicationJob
def perform(user)
UserNotifier.send_notification(user)
end
end
NotifyUserJob.perform_later(User.find(1))
Custom Serializers
class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
def serialize(money)
super("amount" => money.amount, "currency" => money.currency)
end
def deserialize(hash)
Money.new(hash["amount"], hash["currency"])
end
private
def klass
Money
end
end
Rails.application.config.active_job.custom_serializers << MoneySerializer
Mailer Integration
UserMailer.welcome(user).deliver_later
UserMailer.welcome(user).deliver_later(wait: 1.hour)
UserMailer.welcome(user).deliver_later(wait_until: Date.tomorrow.noon)
UserMailer.welcome(user).deliver_now
Testing Jobs
Test Helper Methods
require "test_helper"
class ProcessOrderJobTest < ActiveJob::TestCase
test "processes the order" do
order = orders(:pending)
ProcessOrderJob.perform_now(order)
assert order.reload.processed?
end
test "enqueues job" do
order = orders(:pending)
assert_enqueued_with(job: ProcessOrderJob, args: [order]) do
ProcessOrderJob.perform_later(order)
end
end
test "sends confirmation email" do
order = orders(:pending)
assert_enqueued_emails 1 do
ProcessOrderJob.perform_now(order)
end
end
end
Best Practices
- Keep jobs idempotent - Safe to run multiple times
- Use small arguments - Pass IDs, not large objects
- Handle missing records - Records may be deleted before job runs
- Set appropriate queues - Separate critical from low-priority work
- Monitor failed jobs - Use adapter's UI or custom monitoring
- Test with
perform_now - Test job logic synchronously