Introduction
Rails provides generators and a clean API for creating jobs and controlling when they execute. Understanding the enqueuing options lets you schedule work precisely.
Key Concepts
- perform_later: Enqueues the job for asynchronous execution by a worker process.
- perform_now: Executes the job immediately and synchronously in the current process.
- set: A chainable method that configures options like wait time or queue before enqueuing.
- Global ID: Rails' built-in serialization for Active Record objects.
Real World Context
In a real application, you enqueue jobs from controllers after user actions, from model callbacks after data changes, and from other jobs to build workflows. Knowing the difference between perform_later, perform_now, and set(wait:) is essential.
Deep Dive
Generating a Job
bashbin/rails generate job ProcessImage
This creates app/jobs/process_image_job.rb:
rubyclass ProcessImageJob < ApplicationJob queue_as :default def perform(*args) # Job logic here end end
Enqueuing Options
ruby# Execute as soon as a worker is available ProcessImageJob.perform_later(image.id) # Execute after a delay ProcessImageJob.set(wait: 5.minutes).perform_later(image.id) # Execute at a specific time ReportJob.set(wait_until: Date.tomorrow.noon).perform_later # Execute immediately (synchronous) ProcessImageJob.perform_now(image.id)
Passing Arguments
ruby# GOOD: Pass IDs and primitives SendReportJob.perform_later(user.id, "weekly", Date.today.to_s) # GOOD: Global ID serialization SendReportJob.perform_later(user) # Serialized as gid://app/User/42 # AVOID: Complex objects SendReportJob.perform_later(order.attributes) # Stale data risk
Queue Assignment
rubyclass PaymentJob < ApplicationJob queue_as :critical end class ReportJob < ApplicationJob queue_as :low_priority end
Job Callbacks
rubyclass AuditedJob < ApplicationJob before_enqueue :log_enqueue before_perform :log_start after_perform :log_complete def perform(record_id) # Job logic end private def log_enqueue Rails.logger.info "Enqueueing #{self.class.name}" end end
Common Pitfalls
- Using perform_now in production controllers — This blocks the web request. Reserve it for console, tests, and rake tasks.
- Passing data that changes between enqueue and execution — Pass the record ID and re-fetch inside the job.
Best Practices
- Check if records still exist — A user might be deleted between enqueue and execution.
- Use meaningful queue names — Names like
:critical,:mailers,:importsmake worker allocation clear.
Summary
- Use
bin/rails generate jobto create job classes. perform_laterenqueues asynchronously;perform_nowexecutes synchronously.set(wait:)andset(wait_until:)schedule delayed execution.- Pass IDs or serializable primitives as job arguments.
- Use
queue_asto assign jobs to priority-based queues.
Code Examples
ruby
# Schedule a reminder 24 hours from now
ReminderJob.set(wait: 24.hours).perform_later(user.id, "Complete your profile")
# Schedule for next Monday at 9 AM
ReportJob.set(wait_until: Date.today.next_occurring(:monday).in_time_zone.change(hour: 9)).perform_later