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,
|
2015-06-10 15:17:59 -04:00
|
|
|
enqueuing and executing background jobs.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
After reading this guide, you will know:
|
|
|
|
|
|
|
|
* How to create jobs.
|
|
|
|
* How to enqueue jobs.
|
|
|
|
* How to run jobs in the background.
|
2015-04-19 01:30:32 -04:00
|
|
|
* How to send emails from your application asynchronously.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
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
|
2015-06-10 15:17:59 -04:00
|
|
|
of queuing 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
|
2015-06-10 15:17:59 -04:00
|
|
|
in place. We can then have framework features and other gems build on top of that,
|
|
|
|
without having to 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.
|
|
|
|
|
|
|
|
NOTE: Rails by default comes with an "immediate runner" queuing implementation.
|
|
|
|
That means that each job that has been enqueued will run immediately.
|
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
|
|
|
|
|
2015-08-04 09:24:56 -04:00
|
|
|
def perform(*guests)
|
2014-08-13 06:10:59 -04:00
|
|
|
# Do something later
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2015-08-04 09:24:56 -04:00
|
|
|
Note that you can define `perform` with as many arguments as you want.
|
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
### Enqueue the Job
|
|
|
|
|
|
|
|
Enqueue a job like so:
|
|
|
|
|
|
|
|
```ruby
|
2015-11-08 20:38:13 -05:00
|
|
|
# Enqueue a job to be performed as soon as the queuing system is
|
2015-01-14 01:17:24 -05:00
|
|
|
# free.
|
2015-08-04 09:24:56 -04:00
|
|
|
GuestsCleanupJob.perform_later guest
|
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.
|
2015-08-04 09:24:56 -04:00
|
|
|
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
|
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.
|
2015-08-04 09:24:56 -04:00
|
|
|
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
2015-08-04 09:24:56 -04:00
|
|
|
```ruby
|
|
|
|
# `perform_now` and `perform_later` will call `perform` under the hood so
|
|
|
|
# you can pass as many arguments as defined in the latter.
|
|
|
|
GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')
|
|
|
|
```
|
2014-08-13 06:10:59 -04:00
|
|
|
|
2015-08-04 09:24:56 -04:00
|
|
|
That's it!
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
Job Execution
|
|
|
|
-------------
|
|
|
|
|
2015-06-10 15:17:59 -04:00
|
|
|
For enqueuing and executing jobs you need to set up a queuing backend, that is to
|
|
|
|
say you need to decide for a 3rd-party queuing library that Rails should use.
|
|
|
|
Rails itself does not provide a sophisticated queuing system and just executes the
|
|
|
|
job immediately if no adapter is set.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
### Backends
|
|
|
|
|
2015-06-10 15:17:59 -04:00
|
|
|
Active Job has built-in adapters for multiple queuing 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
|
|
|
|
2015-06-10 15:17:59 -04:00
|
|
|
You can easily set your queuing 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
|
|
|
```
|
|
|
|
|
2015-10-16 19:24:24 -04:00
|
|
|
### Starting the Backend
|
|
|
|
|
|
|
|
Since jobs run in parallel to your Rails application, most queuing libraries
|
2015-06-10 15:17:59 -04:00
|
|
|
require that you start a library-specific queuing service (in addition to
|
2015-10-16 19:24:24 -04:00
|
|
|
starting your Rails app) for the job processing to work. Refer to library
|
|
|
|
documentation for instructions on starting your queue backend.
|
|
|
|
|
|
|
|
Here is a noncomprehensive list of documentation:
|
|
|
|
|
|
|
|
- [Sidekiq](https://github.com/mperham/sidekiq/wiki/Active-Job)
|
|
|
|
- [Resque](https://github.com/resque/resque/wiki/ActiveJob)
|
|
|
|
- [Sucker Punch](https://github.com/brandonhilkert/sucker_punch#active-job)
|
|
|
|
- [Queue Classic](https://github.com/QueueClassic/queue_classic#active-job)
|
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
|
|
|
|
|
2016-01-05 06:28:55 -05:00
|
|
|
# app/jobs/guests_cleanup_job.rb
|
2014-08-22 10:53:31 -04:00
|
|
|
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
|
|
|
|
|
2016-01-05 06:28:55 -05:00
|
|
|
# app/jobs/guests_cleanup_job.rb
|
2014-09-23 16:51:44 -04:00
|
|
|
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)
|
|
|
|
```
|
|
|
|
|
2015-06-10 15:17:59 -04:00
|
|
|
NOTE: Make sure your queuing backend "listens" on your queue name. For some
|
2014-08-22 10:53:31 -04:00
|
|
|
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
|
|
|
|
2015-07-07 15:52:28 -04:00
|
|
|
Internationalization
|
|
|
|
--------------------
|
|
|
|
|
|
|
|
Each job uses the `I18n.locale` set when the job was created. Useful if you send
|
|
|
|
emails asynchronously:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
I18n.locale = :eo
|
|
|
|
|
2015-10-07 11:54:50 -04:00
|
|
|
UserMailer.welcome(@user).deliver_later # Email will be localized to Esperanto.
|
2015-07-07 15:52:28 -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
|
|
|
|
2015-07-02 18:52:44 -04:00
|
|
|
### Deserialization
|
|
|
|
|
|
|
|
GlobalID allows serializing full Active Record objects passed to `#perform`.
|
|
|
|
|
|
|
|
If a passed record is deleted after the job is enqueued but before the `#perform`
|
|
|
|
method is called Active Job will raise an `ActiveJob::DeserializationError`
|
|
|
|
exception.
|
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).
|