Delayed::Job send_later with :run_at

Published July 22, 2009 in Ruby and Rails

I just discovered Delayed::Job’s run_at column and I’m quite happy with it. I hadn’t realized that DJ comes with cron-like scheduling right out of the box, and at stormweight, we’ve been running some DJ-like async tasks with cron/rake because of this (checking email every minute on the minute, for example). One downside of this was that memory usage was inconsistent because every time cron performed a task, rake fired up another rails instance with a 70-100mb memory footprint, even if the DJ workers were sitting there with no work to do.

With run_at, a recurring task can use a tail-recursion-like pattern to reschedule itself — each time the task is run, the first thing it does is to reschedule itself in the future. [Edit: Ok, maybe it’s head recursion, but it happens after the method in question… Regardless, you get the idea]

Now, the crux of this post is that I really like the Object#send_later pattern that DJ provides. DJ lets you just do something like UserMailer.send_later :deliver_some_email. But what if you wanted to do that in ten minutes with run_at (say, for a batch email)? Here’s a quick hack that lets you schedule with send_in or send_at:

class Object
  def send_in(time, method, *args)
    send_at time.from_now, method, *args
  end
  
  def send_at(time, method, *args)
    Delayed::Job.enqueue Delayed::PerformableMethod.new(self, method.to_sym, args), 0, time
  end
end

With this, we can UserMailer.send_in 10.minutes, :deliver_some_email or UserMailer.send_at "#{Date.today} 8pm".to_time, :deliver_some_email. Hurray!