At Avvo we wanted to add some metrics and logging to our background job processing. We're using Resque, so it wouldn't take much to write a module to wrap enqueue and perform to do the logging. After investigating the new ActiveJob abstraction in Rails, we decided there would be benefits to switching.

This post is intended as a brief guide to switching from plain Resque to ActiveJob. In addition, it covers the Minitest test helper and integration with Resque::Scheduler.

What does ActiveJob do?

ActiveJob is built into Rails, and provides a common interface for background jobs. It also adds callback support around enqueuing and performing jobs. By default this is used to add logging to your jobs.

Coming from Resque

Plain Resque was a good starting point years ago, but these days we expect more from our libraries. There's no logging and no metrics hooks. By switching to ActiveJob we get all that for free. Plus it might make switching to Sidekiq easier.

Warning

Remember to keep in mind during upgrading that you may have job in the queue when you deploy your new ActiveJobified code. Because of this, we want to keep the old jobs around, and have them call the new ActiveJob code. Just have old self.perform methods instantiate the new job class and call perform.

Mechanics of using ActiveJob

Switching involves a few steps:

  • Inherit from ActiveJob::Base
  • use queue_as instead of @queue =
  • configure in config/application.rb: ruby config.active_job.queue_adapter = :resque config.active_job.queue_name_prefix = "my_app"
  • Change perform methods from class to instance.
  • Switch from using Resque.enqueue KlassName to KlassName.perform_later.

For instance, we'd change the following class:

class OldCsvParser
  @queue = :csv_parsing

  def self.perform(filename)
    # ... do stuff
  end
end

to:

class CsvParserJob < ActiveJob::Base
  queue_as :csv_parsing

  def perform(filename)
    # ... do the stuff
  end
end

and update the original class to just call the new one:

class OldCsvParser
  @queue = :csv_parsing

  def self.perform(filename)
    CsvParserJob.new.perform(filename)
  end
end

Testing with ActiveJob::TestHelper

Coming from using ResqueUnit the switch to ActiveJob::TestHelper was easy. Include the module in your TestCase and using methods like assert_enqueued_jobs.

For instance, if we enqueued a CsvParserJob from a controller action, our test might look like this:

class CsvParsingControllerTest < ActiveSupport::TestCase
  test "enqueues the job to parse the csv" do
    filename = "/path/to/csv/file.csv"

    assert_enqueued_with(job: CsvParserJob, args: [filename]) do
      post :create, filename: filename
    end
  end
end

Integration with ResqueScheduler

Resque::Scheduler enqueues jobs directly with Resque. You'll need to either change that behavior or wrap jobs in the schedule with a JobWrapper.

Luckily the latter work has already been done, and using the gem ActiveScheduler will wrap the jobs so callbacks are called.

Installation is a snap, just follow the directions in the project's readme to update your Resque::Scheduler initializer.

Downsides: ResqueWeb only shows JobWrapper job classes

Due to the ActiveJob JobWrapper viewing the running jobs in resque-web will no longer show the class of actual job running on the dashboard. Clicking through and viewing the arguments does show the class. This could be a hassle if you often view the job queue.

Similarily, the Schedule tab in resque-web is a little cluttered. But still readable.