2015-01-14 01:22:13 -05:00
|
|
|
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
|
2014-12-23 17:32:50 -05:00
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
Active Job Basics
|
|
|
|
=================
|
|
|
|
|
|
|
|
This guide provides you with all you need to get started in creating,
|
|
|
|
enqueueing and executing background jobs.
|
|
|
|
|
|
|
|
After reading this guide, you will know:
|
|
|
|
|
|
|
|
* How to create jobs.
|
|
|
|
* How to enqueue jobs.
|
|
|
|
* How to run jobs in the background.
|
|
|
|
* How to send emails from your application async.
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2014-08-20 09:33:06 -04:00
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
Introduction
|
|
|
|
------------
|
|
|
|
|
|
|
|
Active Job is a framework for declaring jobs and making them run on a variety
|
|
|
|
of queueing backends. These jobs can be everything from regularly scheduled
|
2014-08-20 15:08:19 -04:00
|
|
|
clean-ups, to billing charges, to mailings. Anything that can be chopped up
|
2014-08-13 06:10:59 -04:00
|
|
|
into small units of work and run in parallel, really.
|
|
|
|
|
|
|
|
|
2014-11-20 10:29:48 -05:00
|
|
|
The Purpose of Active Job
|
2014-08-13 06:10:59 -04:00
|
|
|
-----------------------------
|
|
|
|
The main point is to ensure that all Rails apps will have a job infrastructure
|
|
|
|
in place, even if it's in the form of an "immediate runner". We can then have
|
|
|
|
framework features and other gems build on top of that, without having to
|
2014-08-16 08:28:09 -04:00
|
|
|
worry about API differences between various job runners such as Delayed Job
|
|
|
|
and Resque. Picking your queuing backend becomes more of an operational concern,
|
|
|
|
then. And you'll be able to switch between them without having to rewrite your jobs.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
|
|
|
|
Creating a Job
|
|
|
|
--------------
|
|
|
|
|
2014-08-20 15:08:19 -04:00
|
|
|
This section will provide a step-by-step guide to creating a job and enqueuing it.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
### Create the Job
|
|
|
|
|
2014-08-13 16:05:21 -04:00
|
|
|
Active Job provides a Rails generator to create jobs. The following will create a
|
2014-11-01 14:27:18 -04:00
|
|
|
job in `app/jobs` (with an attached test case under `test/jobs`):
|
2014-08-13 16:05:21 -04:00
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
```bash
|
|
|
|
$ bin/rails generate job guests_cleanup
|
2014-11-01 14:27:18 -04:00
|
|
|
invoke test_unit
|
|
|
|
create test/jobs/guests_cleanup_job_test.rb
|
2014-08-13 06:10:59 -04:00
|
|
|
create app/jobs/guests_cleanup_job.rb
|
|
|
|
```
|
|
|
|
|
2014-08-13 16:05:21 -04:00
|
|
|
You can also create a job that will run on a specific queue:
|
|
|
|
|
|
|
|
```bash
|
|
|
|
$ bin/rails generate job guests_cleanup --queue urgent
|
|
|
|
```
|
|
|
|
|
|
|
|
If you don't want to use a generator, you could create your own file inside of
|
2014-08-20 09:33:06 -04:00
|
|
|
`app/jobs`, just make sure that it inherits from `ActiveJob::Base`.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
2014-08-20 15:08:19 -04:00
|
|
|
Here's what a job looks like:
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class GuestsCleanupJob < ActiveJob::Base
|
|
|
|
queue_as :default
|
|
|
|
|
2014-08-23 09:07:49 -04:00
|
|
|
def perform(*args)
|
2014-08-13 06:10:59 -04:00
|
|
|
# Do something later
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
### Enqueue the Job
|
|
|
|
|
|
|
|
Enqueue a job like so:
|
|
|
|
|
|
|
|
```ruby
|
2015-01-14 01:17:24 -05:00
|
|
|
# Enqueue a job to be performed as soon the queueing system is
|
|
|
|
# free.
|
2014-11-01 14:27:18 -04:00
|
|
|
MyJob.perform_later record
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
```ruby
|
2014-11-01 14:27:18 -04:00
|
|
|
# Enqueue a job to be performed tomorrow at noon.
|
|
|
|
MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record)
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
```ruby
|
2014-11-01 14:27:18 -04:00
|
|
|
# Enqueue a job to be performed 1 week from now.
|
|
|
|
MyJob.set(wait: 1.week).perform_later(record)
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
That's it!
|
|
|
|
|
|
|
|
|
|
|
|
Job Execution
|
|
|
|
-------------
|
|
|
|
|
2014-08-20 15:08:19 -04:00
|
|
|
If no adapter is set, the job is immediately executed.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
### Backends
|
|
|
|
|
2014-09-27 10:22:15 -04:00
|
|
|
Active Job has built-in adapters for multiple queueing backends (Sidekiq,
|
2014-09-21 16:20:23 -04:00
|
|
|
Resque, Delayed Job and others). To get an up-to-date list of the adapters
|
|
|
|
see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
|
|
|
|
|
2014-11-20 10:29:48 -05:00
|
|
|
### Setting the Backend
|
2014-09-21 16:20:23 -04:00
|
|
|
|
2014-11-20 10:29:48 -05:00
|
|
|
You can easily set your queueing backend:
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
```ruby
|
2014-11-20 10:29:48 -05:00
|
|
|
# config/application.rb
|
|
|
|
module YourApp
|
|
|
|
class Application < Rails::Application
|
2015-01-14 01:17:24 -05:00
|
|
|
# Be sure to have the adapter's gem in your Gemfile
|
|
|
|
# and follow the adapter's specific installation
|
|
|
|
# and deployment instructions.
|
2014-11-20 10:29:48 -05:00
|
|
|
config.active_job.queue_adapter = :sidekiq
|
|
|
|
end
|
|
|
|
end
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
2014-08-20 09:33:06 -04:00
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
Queues
|
|
|
|
------
|
|
|
|
|
2014-08-20 15:08:19 -04:00
|
|
|
Most of the adapters support multiple queues. With Active Job you can schedule
|
2014-08-20 09:33:06 -04:00
|
|
|
the job to run on a specific queue:
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
class GuestsCleanupJob < ActiveJob::Base
|
2014-08-16 08:28:09 -04:00
|
|
|
queue_as :low_priority
|
2014-08-13 06:10:59 -04:00
|
|
|
#....
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2014-08-25 10:34:50 -04:00
|
|
|
You can prefix the queue name for all your jobs using
|
2014-08-22 10:53:31 -04:00
|
|
|
`config.active_job.queue_name_prefix` in `application.rb`:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
# config/application.rb
|
|
|
|
module YourApp
|
|
|
|
class Application < Rails::Application
|
|
|
|
config.active_job.queue_name_prefix = Rails.env
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# app/jobs/guests_cleanup.rb
|
|
|
|
class GuestsCleanupJob < ActiveJob::Base
|
|
|
|
queue_as :low_priority
|
|
|
|
#....
|
|
|
|
end
|
|
|
|
|
2014-08-25 10:34:50 -04:00
|
|
|
# Now your job will run on queue production_low_priority on your
|
2015-01-14 01:17:24 -05:00
|
|
|
# production environment and on staging_low_priority
|
|
|
|
# on your staging environment
|
2014-08-22 10:53:31 -04:00
|
|
|
```
|
|
|
|
|
2014-12-12 12:27:30 -05:00
|
|
|
The default queue name prefix delimiter is '\_'. This can be changed by setting
|
2014-09-23 16:51:44 -04:00
|
|
|
`config.active_job.queue_name_delimiter` in `application.rb`:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
# config/application.rb
|
|
|
|
module YourApp
|
|
|
|
class Application < Rails::Application
|
|
|
|
config.active_job.queue_name_prefix = Rails.env
|
|
|
|
config.active_job.queue_name_delimiter = '.'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# app/jobs/guests_cleanup.rb
|
|
|
|
class GuestsCleanupJob < ActiveJob::Base
|
|
|
|
queue_as :low_priority
|
|
|
|
#....
|
|
|
|
end
|
|
|
|
|
|
|
|
# Now your job will run on queue production.low_priority on your
|
2015-01-14 01:17:24 -05:00
|
|
|
# production environment and on staging.low_priority
|
|
|
|
# on your staging environment
|
2014-09-23 16:51:44 -04:00
|
|
|
```
|
|
|
|
|
2014-10-31 14:46:43 -04:00
|
|
|
If you want more control on what queue a job will be run you can pass a `:queue`
|
|
|
|
option to `#set`:
|
2014-08-25 10:34:50 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
MyJob.set(queue: :another_queue).perform_later(record)
|
|
|
|
```
|
|
|
|
|
2014-10-31 14:46:43 -04:00
|
|
|
To control the queue from the job level you can pass a block to `#queue_as`. The
|
|
|
|
block will be executed in the job context (so you can access `self.arguments`)
|
2014-08-25 10:34:50 -04:00
|
|
|
and you must return the queue name:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
class ProcessVideoJob < ActiveJob::Base
|
|
|
|
queue_as do
|
|
|
|
video = self.arguments.first
|
|
|
|
if video.owner.premium?
|
|
|
|
:premium_videojobs
|
|
|
|
else
|
|
|
|
:videojobs
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def perform(video)
|
2015-02-20 12:47:55 -05:00
|
|
|
# Do process video
|
2014-08-25 10:34:50 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
ProcessVideoJob.perform_later(Video.last)
|
|
|
|
```
|
|
|
|
|
2014-08-22 10:53:31 -04:00
|
|
|
NOTE: Make sure your queueing backend "listens" on your queue name. For some
|
|
|
|
backends you need to specify the queues to listen to.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
|
|
|
|
Callbacks
|
|
|
|
---------
|
|
|
|
|
2014-12-31 05:21:37 -05:00
|
|
|
Active Job provides hooks during the life cycle of a job. Callbacks allow you to
|
|
|
|
trigger logic during the life cycle of a job.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
### Available callbacks
|
|
|
|
|
2014-08-20 09:33:06 -04:00
|
|
|
* `before_enqueue`
|
|
|
|
* `around_enqueue`
|
|
|
|
* `after_enqueue`
|
|
|
|
* `before_perform`
|
|
|
|
* `around_perform`
|
|
|
|
* `after_perform`
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
### Usage
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
class GuestsCleanupJob < ActiveJob::Base
|
|
|
|
queue_as :default
|
|
|
|
|
|
|
|
before_enqueue do |job|
|
2015-02-20 12:47:55 -05:00
|
|
|
# Do something with the job instance
|
2014-08-13 06:10:59 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
around_perform do |job, block|
|
2015-02-20 12:47:55 -05:00
|
|
|
# Do something before perform
|
2014-08-13 06:10:59 -04:00
|
|
|
block.call
|
2015-02-20 12:47:55 -05:00
|
|
|
# Do something after perform
|
2014-08-13 06:10:59 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def perform
|
|
|
|
# Do something later
|
|
|
|
end
|
|
|
|
end
|
2014-08-16 08:28:09 -04:00
|
|
|
```
|
|
|
|
|
2014-08-20 09:33:06 -04:00
|
|
|
|
2014-11-16 10:22:33 -05:00
|
|
|
Action Mailer
|
2014-08-16 08:28:09 -04:00
|
|
|
------------
|
2014-08-20 09:33:06 -04:00
|
|
|
|
2014-08-16 08:28:09 -04:00
|
|
|
One of the most common jobs in a modern web application is sending emails outside
|
|
|
|
of the request-response cycle, so the user doesn't have to wait on it. Active Job
|
2014-08-20 15:08:19 -04:00
|
|
|
is integrated with Action Mailer so you can easily send emails asynchronously:
|
2014-08-16 08:28:09 -04:00
|
|
|
|
|
|
|
```ruby
|
2014-08-20 08:34:37 -04:00
|
|
|
# If you want to send the email now use #deliver_now
|
|
|
|
UserMailer.welcome(@user).deliver_now
|
2014-08-13 06:10:59 -04:00
|
|
|
|
2014-08-21 17:27:40 -04:00
|
|
|
# If you want to send the email through Active Job use #deliver_later
|
2014-08-16 08:28:09 -04:00
|
|
|
UserMailer.welcome(@user).deliver_later
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
2014-08-20 09:33:06 -04:00
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
GlobalID
|
|
|
|
--------
|
2014-11-01 14:27:18 -04:00
|
|
|
|
2014-08-20 09:33:06 -04:00
|
|
|
Active Job supports GlobalID for parameters. 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. Before, jobs would look like this:
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
```ruby
|
2014-11-01 14:27:18 -04:00
|
|
|
class TrashableCleanupJob < ActiveJob::Base
|
2014-08-13 06:10:59 -04:00
|
|
|
def perform(trashable_class, trashable_id, depth)
|
|
|
|
trashable = trashable_class.constantize.find(trashable_id)
|
|
|
|
trashable.cleanup(depth)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
Now you can simply do:
|
|
|
|
|
|
|
|
```ruby
|
2014-11-01 14:27:18 -04:00
|
|
|
class TrashableCleanupJob < ActiveJob::Base
|
2014-08-13 06:10:59 -04:00
|
|
|
def perform(trashable, depth)
|
|
|
|
trashable.cleanup(depth)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2014-10-29 17:36:16 -04:00
|
|
|
This works with any class that mixes in `GlobalID::Identification`, which
|
2015-01-08 04:09:41 -05:00
|
|
|
by default has been mixed into Active Record classes.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
|
|
|
|
Exceptions
|
|
|
|
----------
|
2014-08-20 09:33:06 -04:00
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
Active Job provides a way to catch exceptions raised during the execution of the
|
|
|
|
job:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
class GuestsCleanupJob < ActiveJob::Base
|
|
|
|
queue_as :default
|
|
|
|
|
2014-08-23 01:40:49 -04:00
|
|
|
rescue_from(ActiveRecord::RecordNotFound) do |exception|
|
2015-02-20 12:47:55 -05:00
|
|
|
# Do something with the exception
|
2014-08-13 06:10:59 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def perform
|
|
|
|
# Do something later
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
2015-04-16 02:46:12 -04:00
|
|
|
|
|
|
|
|
|
|
|
Job Testing
|
|
|
|
--------------
|
|
|
|
|
|
|
|
You can find detailed instructions on how to test your jobs in the
|
|
|
|
[testing guide](testing.html#testing-jobs).
|