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.
|
|
|
|
|
|
|
|
|
|
|
|
The Purpose of the Active Job
|
|
|
|
-----------------------------
|
|
|
|
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-08-20 09:33:06 -04:00
|
|
|
job in `app/jobs`:
|
2014-08-13 16:05:21 -04:00
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
```bash
|
|
|
|
$ bin/rails generate job guests_cleanup
|
|
|
|
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
|
|
|
|
create app/jobs/guests_cleanup_job.rb
|
|
|
|
```
|
|
|
|
|
2014-08-13 06:10:59 -04:00
|
|
|
As you can see, you can generate jobs just like you use other generators with
|
|
|
|
Rails.
|
|
|
|
|
2014-08-13 16:05:21 -04:00
|
|
|
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
|
2014-08-25 10:34:50 -04:00
|
|
|
MyJob.perform_later record # Enqueue a job to be performed as soon the queueing system is free.
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
```ruby
|
2014-08-25 10:34:50 -04:00
|
|
|
MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) # Enqueue a job to be performed tomorrow at noon.
|
2014-08-13 06:10:59 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
```ruby
|
2014-08-25 10:34:50 -04:00
|
|
|
MyJob.set(wait: 1.week).perform_later(record) # Enqueue a job to be performed 1 week from now.
|
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
|
|
|
|
|
|
|
|
Active Job has adapters for the following queueing backends:
|
|
|
|
|
|
|
|
* [Backburner](https://github.com/nesquena/backburner)
|
|
|
|
* [Delayed Job](https://github.com/collectiveidea/delayed_job)
|
|
|
|
* [Qu](https://github.com/bkeepers/qu)
|
|
|
|
* [Que](https://github.com/chanks/que)
|
2014-08-22 10:44:48 -04:00
|
|
|
* [QueueClassic 2.x](https://github.com/ryandotsmith/queue_classic/tree/v2.2.3)
|
|
|
|
* [Resque 1.x](https://github.com/resque/resque/tree/1-x-stable)
|
2014-08-13 06:10:59 -04:00
|
|
|
* [Sidekiq](https://github.com/mperham/sidekiq)
|
|
|
|
* [Sneakers](https://github.com/jondot/sneakers)
|
|
|
|
* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch)
|
|
|
|
|
|
|
|
#### Backends Features
|
|
|
|
|
2014-08-24 08:29:20 -04:00
|
|
|
| | Async | Queues | Delayed | Priorities | Timeout | Retries |
|
|
|
|
|-----------------------|-------|--------|-----------|------------|---------|---------|
|
|
|
|
| **Backburner** | Yes | Yes | Yes | Yes | Job | Global |
|
|
|
|
| **Delayed Job** | Yes | Yes | Yes | Job | Global | Global |
|
|
|
|
| **Que** | Yes | Yes | Yes | Job | No | Job |
|
|
|
|
| **Queue Classic** | Yes | Yes | No* | No | No | No |
|
|
|
|
| **Resque** | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
|
|
|
|
| **Sidekiq** | Yes | Yes | Yes | Queue | No | Job |
|
|
|
|
| **Sneakers** | Yes | Yes | No | Queue | Queue | No |
|
|
|
|
| **Sucker Punch** | Yes | Yes | No | No | No | No |
|
|
|
|
| **Active Job Inline** | No | Yes | N/A | N/A | N/A | N/A |
|
|
|
|
| **Active Job** | Yes | Yes | Yes | No | No | No |
|
|
|
|
|
|
|
|
NOTE:
|
|
|
|
* Queue Classic does not support Job scheduling. However you can implement this
|
|
|
|
yourself or you can use the queue_classic-later gem. See the documentation for
|
|
|
|
ActiveJob::QueueAdapters::QueueClassicAdapter.
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
### Change Backends
|
|
|
|
|
2014-08-20 15:08:19 -04:00
|
|
|
You can easily change your adapter:
|
2014-08-13 06:10:59 -04:00
|
|
|
|
|
|
|
```ruby
|
|
|
|
# be sure to have the adapter gem in your Gemfile and follow the adapter specific
|
|
|
|
# installation and deployment instructions
|
2014-08-15 16:32:08 -04:00
|
|
|
YourApp::Application.config.active_job.queue_adapter = :sidekiq
|
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
|
|
|
|
# production environment and on beta_low_priority on your beta
|
|
|
|
# environment
|
2014-08-22 10:53:31 -04:00
|
|
|
```
|
|
|
|
|
2014-08-25 10:34:50 -04:00
|
|
|
If you want more control on what queue a job will be run you can pass a :queue
|
|
|
|
option to #set:
|
|
|
|
|
|
|
|
```ruby
|
|
|
|
MyJob.set(queue: :another_queue).perform_later(record)
|
|
|
|
```
|
|
|
|
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
# do process video
|
|
|
|
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-08-20 15:08:19 -04:00
|
|
|
Active Job provides hooks during the lifecycle of a job. Callbacks allow you to
|
2014-08-20 09:33:06 -04:00
|
|
|
trigger logic during the lifecycle 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|
|
2014-08-26 01:01:28 -04:00
|
|
|
# do something with the job instance
|
2014-08-13 06:10:59 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
around_perform do |job, block|
|
|
|
|
# do something before perform
|
|
|
|
block.call
|
|
|
|
# do something after perform
|
|
|
|
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-08-16 08:28:09 -04:00
|
|
|
ActionMailer
|
|
|
|
------------
|
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-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
|
|
|
|
class TrashableCleanupJob
|
|
|
|
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
|
|
|
|
class TrashableCleanupJob
|
|
|
|
def perform(trashable, depth)
|
|
|
|
trashable.cleanup(depth)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2014-08-20 09:33:06 -04:00
|
|
|
This works with any class that mixes in `ActiveModel::GlobalIdentification`, which
|
2014-08-13 06:10:59 -04:00
|
|
|
by default has been mixed into Active Model classes.
|
|
|
|
|
|
|
|
|
|
|
|
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|
|
2014-08-13 06:10:59 -04:00
|
|
|
# do something with the exception
|
|
|
|
end
|
|
|
|
|
|
|
|
def perform
|
|
|
|
# Do something later
|
|
|
|
end
|
|
|
|
end
|
|
|
|
```
|