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!