1. What is Active Job?
The Active Job Rails framework allows you to declare background jobs and execute them on a queuing backend. It provides a consistent, high-level interface for common asynchronous tasks such as sending emails, processing data, or performing periodic maintenance tasks.
The goal of background jobs is to move long-running or non-critical work out of the HTTP request-response cycle and into a background queue (such as the default Solid Queue), and keep the web requests fast and responsive. This separation allows applications to perform work asynchronously, scale background processing independently, and execute multiple tasks in parallel without blocking user interactions.
2. Creating Jobs
This section provides a step-by-step guide for defining a job Ruby class and
then using the perform_* method to enqueue work to be executed in the
background.
2.1. Defining a Job
Active Job provides a Rails generator to create jobs. The following will create
a job in the app/jobs directory (with tests under test/jobs):
$ bin/rails generate job guests_cleanup
invoke test_unit
create test/jobs/guests_cleanup_job_test.rb
create app/jobs/guests_cleanup_job.rb
If you don't want to use a generator, you can create your own file inside of
app/jobs and define a class that inherits from ApplicationJob.
Here's what a job looks like:
class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
# Do something later
end
end
Note that you can define the perform method inside a job class with as many
arguments as you want.
If your application uses a custom abstract job base class instead of
ApplicationJob, you can use the --parent option with the generator. The
parent class must itself inherit from ApplicationJob. This can be useful for
grouping related functionality in one place.
For example, given a custom abstract job class using queue_as:
class PaymentJob < ApplicationJob
queue_as :payments
end
You can generate a new job that inherits from it:
$ bin/rails generate job process_payment --parent=payment_job
The above creates a class that will use the payments queue:
class ProcessPaymentJob < PaymentJob
def perform(*args)
# Do something later, uses the "payments" queue
end
end
2.2. Calling the perform_* Methods
Once you have defined a job class with a perform method, you'd typically call
it using perform_later to enqueue the work to be executed on a queuing
backend. Or use perform_now if you want the job to execute immediately
without queueing. Both perform_later and perform_now call perform under
the hood.
In the examples below, the methods can be called from anywhere in your Rails application, most commonly from controllers, models, or other jobs.
# To run a job immediately without enqueuing it
GuestsCleanupJob.perform_now(guest)
# To enqueue a job to be performed later
GuestsCleanupJob.perform_later(guest)
Use the
set
method to specify exactly when to perform a job.
# Enqueue a job to be performed tomorrow at noon
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
# Enqueue a job to be performed one week from now
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
Since both perform_now and perform_later forward their arguments to
perform, you can pass as many arguments as defined in perform, including
keyword arguments:
GuestsCleanupJob.perform_later(guest1, guest2, filter: "some_filter")
2.2.1. Example: Sending Email
One of the most common jobs in a modern web application is sending emails to users. Active Job can do this outside of the request-response cycle, so the user doesn't have to wait on it. Active Job is integrated with Action Mailer so you can easily send emails asynchronously:
# If you want to send the email now, use #deliver_now
UserMailer.welcome(@user).deliver_now
# If you want to send the email asynchronously, use #deliver_later
UserMailer.welcome(@user).deliver_later
The deliver_now and deliver_later methods are Action Mailer's counterparts
to perform_now and perform_later. Under the hood, deliver_later works by
enqueuing an ActionMailer::MailDeliveryJob — a built-in Active Job job that
Rails provides — which goes through the same queuing pipeline as any job you
define yourself, and will eventually call the perform method in your Job
class.
2.3. Supported Argument Types for perform
ActiveJob supports the following types of arguments by default:
- Basic types (
NilClass,String,Integer,Float,BigDecimal,TrueClass,FalseClass) SymbolDateTimeDateTimeActiveSupport::TimeWithZoneActiveSupport::DurationHash(Keys should be ofStringorSymboltype)ActiveSupport::HashWithIndifferentAccessArrayRangeModuleClass
Active Job supports GlobalID for arguments. This makes it possible to pass live Active Record objects to your job instead of class/id pairs, which you then have to manually deserialize.
For example, instead of having to do something like this:
class GuestsCleanupJob < ApplicationJob
def perform(guests_class, guests_id, depth)
guests = guests_class.constantize.find(guests_id)
guest.cleanup(depth)
end
end
Using GlobalID, you can simply do:
class GuestsCleanupJob < ApplicationJob
def perform(guest, depth)
guest.cleanup(depth)
end
end
This works with any class that mixes in GlobalID::Identification, which is
mixed into Active Record by default.
2.3.1. Add Custom Types by Defining Serializers
You can extend the list of supported argument types by defining your own
serializer for your custom types. A serializer needs three methods: serialize,
deserialize, and klass.
The serialize method converts an object into a simpler representation using
only supported types. The recommended approach is to return a Hash with string
keys, calling super to let Active Job merge in the serializer's type
information:
# app/serializers/money_serializer.rb
class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
def serialize(money)
super(
"amount" => money