Queuing and Scheduling in Rails with Delayed::Job

Rails has several popular options for queuing or scheduling tasks. When I was looking at the alternatives for my app TimeVault I was faced with something of a difficult choice. Two of the most popular libraries, Resque and Sidekiq, require Redis. At the time, I didn’t want to mess around with an additional component to deploy, so I used Delayed::Job instead.

Delayed::Job (DJ) supports multiple backends. Most importantly for our purposes, it has solid support for ActiveRecord. There are performance reasons for choosing Redis over ActiveRecord, but for a small learning app these are typically safe to ignore.

TimeVault allows users to set Pomodoro timers and receive alerts when they are complete. Each timer has one or more Intervals with a start and stop time. When a timer starts, an Interval is created, which then creates an IntervalWorker:

def create_interval_worker
  Delayed::Job.enqueue(IntervalWorker.new(id), run_at: when_to_run)
  save
end

DJ supplies an enqueue class method. Here, I’m passing in a new IntervalWorker and using the run_at option to delay instantiation of the worker until the interval expires. DJ expects enqueued objects to have a perform method that will be called on execution. Here, the IntervalWorker calls complete! on the interval that created it.

class IntervalWorker
  def initialize(interval_id)
    @interval_id = interval_id
  end

  def perform
    interval = Interval.where(id: @interval_id).first
    interval.complete! if interval.present?
  end
end

complete! ends the interval by setting its end attribute to the current time. Note how the service object, IntervalWorker, is quite simple. All the actual logic is built into the model, Interval. This is an example of separation of concerns. Modifying the Interval is not a concern of the IntervalWorker.

I highly recommend the Delayed::Job RailsCast to get started with DJ. Good luck!

comments powered by Disqus