So I was working on an Intridea project where I needed to send out a daily digest email.  The emails needed to be sent out on a scheduled interval, not as the result of some user action.  This meant that it would need to be handled outside of the normal request/response cycle.

Typically in the Rails world, when someone mentions long-running, or background tasks, you think either BackgrounDrb or Starling/Workling.  Those tools are fine and all -- actually, don't get me started on BackgrounDrb -- but I wanted something a bit more simple.

I didn't need queueing, or jobs, or anything like that.  I had only one task to manage.  I just want something that will say, "Once a day, do [task]", and that's all.

Also, I really didn't want to have to monkey with deployment tasks, be it with Vlad or Capistrano or whatever.  I wanted to just deploy as usual, and have it all just work, no extra processes to start or tasks to run.

This seemed like a tall order, but I began my search anyway.  Google led me to a number of pages about a number of tools, all of which included one or more of the things I was trying to avoid.  Of all places, I finally ended up on this page from the Ruby On Rails wiki.

Despite having only a one line mention near the bottom of the page, I decided to check out rufus-scheduler, and it turned out to be exactly what I was looking for.  There was no database table, queueing mechanism, or separate process to manage.  Just a simple scheduler to call out to your existing ruby code.

It couldn't get any simpler.  Here's how I set it up...

First, install the gem.

sudo gem install rufus-scheduler

I also froze it into vendor/gems by declaring the gem in the initializer block of environment.rb and then running rake gems:unpack.

Then I created a file called task_scheduler.rb in my config/initializers directory.   Inside this file is where the magic happens.  This is where it all goes down.  Are you ready?  Here it is...

scheduler = Rufus::Scheduler.start_new

scheduler.every("1m") do
  DailyDigest.send_digest!
end

Yeah, that's it. Seriously.

I was in disbelief myself until I started up the server and watched it send me an email.  In it's current state, it would send the digest every minute, which is not what I wanted.  Fortunately, the rufus-scheduler provides several ways to schedule your tasks.  Once I was ready, I changed it to more of a cron-style scheduler.

scheduler = Rufus::Scheduler.start_new

# Send the digest every day at noon
scheduler.cron("0 12 * * *") do
  DailyDigest.new.send_digest!
end

You could also use scheduler.in or scheduler.at for one time tasks set to run at some point in the future.

I know a guy who's from "the future", but that's another story.

Anyway, there you have it.  Dead simple task scheduling in Rails.  It really doesn't get any easier than that.

Comments (0)    rails task-scheduling

So I recently spent several days upgrading an outdated backgroundrb install to the latest version.  The Backgroundrb documentation is a bit sparse, so needless to say, I quickly became an active member of the mailing list.

Actually, to be honest, I didn't end up joining the mailing list until after I spent nearly an entire day pulling my hair out over a very strange bug.

Basically, I would create a new worker, send it some work to do, and then nothing would happen.  The code looked something like this:

key = MiddleMan.new_worker(:worker => :prince_xml_worker, :worker_key => worker_key)
worker = MiddleMan.worker(:prince_xml_worker, key)
worker.async_build_pdf(:arg => html_string) 

(I was working on pdf generation in case you couldn't tell)

A little tailing of the background_debug log told me that I was requesting work on an invalid worker.  An invalid worker?  How could it not be valid?  I just created the freakin thing.  I thought maybe the key was wrong or something, so I added a debugger after the 'new_worker' line and tested it out.  To my frustration, the generated key was perfectly valid, as was the worker retrieved from the middleman.

WTF?

I even managed to call the async worker method from the console and it worked perfectly.  But anytime that I would remove the debugger and let it run its course, nothing would happen.  It seemed ridiculous at first, but I thought maybe it just needs a second before its ready.  Nah...  That couldn't be it.  Just for kicks I put in a half-second sleep call where the debugger was previously, like so:

key = MiddleMan.new_worker(:worker => :prince_xml_worker, :worker_key => worker_key)
worker = MiddleMan.worker(:prince_xml_worker, key)
sleep(0.5)
worker.async_build_pdf(:arg => html_string) 

...and it worker perfectly.  

This just seemed crazy, so I got on the mailing list, described my problem and had a response within the hour.  I couldn't believe it, but i was right.  It said that if you're developing on OSX that backgroundrb basically needs a split second to start up the worker, before it can be used.  Since only our development environments were OSX, I conditionally enabled the sleep command based on the RAILS_ENV.  Unfortunately, our staging server, which is a Gentoo machine had the same problem, so I had to update the code accordinly.

ALSO...

Whatever you do, don't put 'puts' statements in your workers.  I ran into the situation where my debugging of code was actually causing it to break, which wasted more time than I'd care to admit.

Comments (0)    task-scheduling backgroundrb