Hey guys! I hope you are doing great. Recently I worked on a Spree extension to index products to an Elasticsearch server. To do this, I used an enqueued job to send the model to Elasticsearch. In the outside world, you can use different queueing backends to accomplish this, like Sidekiq, Delayed Job, etc.
Since I was developing an extension, I wanted to make it work with any queueing backend, or at least, with several ones. That's where Active Job comes handy.
What is Active Job?
From the Rails guides:
"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 clean-ups, to billing charges, to mailings. Anything that can be chopped up into small units of work and run in parallel, really."
Rails 4.2
This ability comes out of the box in Rails 4.2. To create a job, you just do the following:
$ rails g job elasticsearch_index
With this, Rails will create a Ruby file. In this case, it will create elasticsearch_index_job.rb
file inside app/jobs
folder.
Here's how our job file looks like:
[ecko_code_highlight language="ruby"]
class ElasticsearchIndexJob < ActiveJob::Base
queue_as :default
def perform(*args)
# Do something later
end
end
[/ecko_code_highlight]
Active Job for Rails version below 4.2
If your Rails app uses a Rails version lower than 4.2, you can still use Active Job. For this, you just add the gem to your Gemfile
.
gem 'activejob'
And don't forget to install it.
$ bundle install
After this, you need to create your job file manually. Don't forget to put it inside app/jobs
and to make it extends from ActiveJob::Base
.
Using Active Job
There are some differences between using Active Job in Rails 4.2 and lower versions.
Rails 4.2
Equeueing a job
To enqueue our job, we just simply do:
MyCustomJob.perform_later record
This will perform our job as soon as a queueing system is free. To perform it in a given time, you can do this:
MyCustomJob.set(wait_until: Date.tomorrow.noon).perform_later(record)
# or
MyCustomJob.set(wait: 1.week).perform_later(record)
Set a queueing backend
By default, Active Job executes the jobs immediately (inline
option). To use a queueing backend, is necessary to set it inside our config/application.rb
file.
config.active_job.queue_adapter = :sidekiq
# or
config.active_job.queue_adapter = :delayed_job
Here you can find the list of queueing backends that Active Job supports.
Lower versions
As I said before, implementing Active Job for lower versions changes a little bit.
Equeueing a job
To enqueue our job, we just simply do:
MyCustomJob.enqueue record
As you can see, to enqueues jobs we use enqueue
method instead of perform_later
.
Enqueue a job to be performed at an interval from now:
MyCustomJob.enqueue_in(1.week, record)
Enqueue a job to be performed at an explicit point in time:
MyCustomJob.enqueue_at(Date.tomorrow.midnight, record)
Set a queueing backend
Another difference is setting a queueing backend. To do this, we need to create an initializer.
# config/initializers/active_job.rb
require 'active_job'
# or any other supported backend such as :sidekiq or :delayed_job
ActiveJob::Base.queue_adapter = :inline
This is crucial because it's necessary to have this initializer with at least an :inline
queue adapter. You may notice the require 'active_job'
line, that's because Active Job doesn't load itself into our Rails app by default.
Note For lower versions, there are no callback mechanisms such before_enqueue
, before_perform
, etc.
GlobalID and Active Model GlobalID
Active Job supports GlobalID for parameters as well as Active Model GlobalID for lower versions. This makes it possible to pass live Active Record objects to your job instead of class/id pairs, which you then have to deserialize manually.
Before:
[ecko_code_highlight language="ruby"]
class TrashableCleanupJob < ActiveJob::Base
def perform(trashable_class, trashable_id, depth)
trashable = trashable_class.constantize.find(trashable_id)
trashable.cleanup(depth)
end
end
[/ecko_code_highlight]
After:
[ecko_code_highlight language="ruby"]
class TrashableCleanupJob < ActiveJob::Base
def perform(trashable, depth)
trashable.cleanup(depth)
end
end
[/ecko_code_highlight]
Conclusion
As you saw here, Active Job is pretty neat by allowing queueing jobs in a lot of queueing backends such as Sidekiq, Delayed Job and so on. This is very convenience because is built on Rails 4.2 (or a gem for lower versions), and it's pretty easy to use. It also supports GlobalID, so we can pass live Active Record objects without having to deserialize them manually.
For more information, I recommend you to go to Active Job Basics to learn more. And that's it. Thanks for reading and see you in the next one!