Recently I came to a situation where I need to take an action based on time. For example, a web application helps organizing an event and 48 hours before its scheduled time, it'll send out a reminder email to all attendees. It's not triggered by view, but time.

Setting up a cron job or a delayed job can both achieve the goal. Let's look at cron job.
In Rails, there are many gems available for scheduling. We are going to look at the following 4 gems.

  • Whenever
  • Rufus scheduler
  • Resque scheduler
  • Clockwork

Whenever####

Whenever is a Ruby gem that provides a clear syntax for writing and deploying cron jobs.

How to use?####

In Rails 4, you can put the gem in Gemfile and run bundle install. An example of having a schedule.rb file under config directory

every 1.day, :at=>'5:00am' do
	your script
end

You could do every 2.hours, every :sunday or even use cron syntax every '0 0 2 * *'

Then whenever translate that into crontab job by issuing:

whenever --update-crontab my_cron_name

my_cron_name is just a name for your cronjob that can be updated easily later on.

Comments####

The process is pretty clear and straighforward. The problem is that whenever reloads the rails environment every time it's executed, which is a real problem when your tasks are frequent or have a lot of initialization work to do.

Rufus scheduler####

rufus-scheduler is a Ruby gem for scheduling pieces of code (jobs). It understands running a job AT a certain time, IN a certain time, EVERY x time or simply via a CRON statement.

The scheduler doesn’t do any server-side tasks to get jobs to run, but relies on its scheduler being run up and maintained persistently.

How to use?####

First, put rufus-scheduler in Gemfile and run bundle install. Then add following lines in config/initializers/task_schedule.rb

require 'rubygems'
require 'rufus/scheduler'

scheduler = Rufus::Scheduler.start_new

scheduler.every("10m") do
  ***your script***
end

You could also do run a job in 20 minutes or at specific time or even use cron schedule syntax

scheduler.cron("0,2 * * * *") do
	`rake "my_task"`
end
 
scheduler.in '20m' do
	`rake "my_task"`
end
 
scheduler.at 'Thu Aug 21 07:31:43 +0500 2014' do
	`rake "my_task"`
end

Comments####

You just need to include one file in config/initializers and add your scheduled tasks. When Rails server starts, that tasks are done or performed at specific time.
Unlike whenever, you won't need to translate its schedule into crontab. Rather Rails app processes ties the scheduler into action. Rufus runs inside application when server initilize. If you do change the schedule file, you have to restart Rails server.

Resque scheduler####

Resque-scheduler is an extension to Resque (Resque is a Redis-backed library for creating background jobs, placing those jobs on multiple queues, and processing them later.) that adds support for queueing items in the future.

How to use?####

In Gemfile,

gem 'resque'
gem 'resque-scheduler'

Run bundle install.
In order to configure the scheduler, we will need to either use a Redis service or set up our own Redis Server. We register at Redis To Go and get an instance. Copy the URL and modify config/initializers/resque.rb

ENV["REDISTOGO_URL"] ||= "redis://username:password@host:1234/"
uri = URI.parse(ENV["REDISTOGO_URL"])
 
Resque.redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
require 'resque_scheduler'

A schedule file is a list of Resque job classes with arguments and a schedule frequency in crontab syntax. The file is usually stored in a YAML format:

SendReminderEmails:
	cron: "0 5 * * *"
    
ClearJobQueue:
	cron: "30 6 * 1 *"
    class: "ClearJob"
    queue: high
    args:
    description: "This is to clear all job queues"

To load this schedule file, add the following line to config/initializers/resque.rb

Resque.schedule = YAML.load_file("#{Rails.root}/config/resque_schedule.yml")

Also need to add a rake file under tasks

$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
require 'resque_scheduler/tasks'

Then run command

$rake resque:scheduler

to start the scheduler process

Comments####

Resque-scheduler builds on top of Resque (task management) and rufus-scheduler (schedule management).
It is a powerful tool as it not only supports cronjob schedule but also delayed jobs. But it requires a Redis server. If you have a bunch of jobs to schedule, Redis can take the load off from your server and control them from a single place. If you're just up for a simple scheduling job (don't need queuing mechanism), this could be a overkill.

Clockwork####

Clockwork is a cron replacement. It runs as a lightweight, long-running Ruby process which sits alongside your web processes (Mongrel/Thin) and your worker processes (DJ/Resque/Minion/Stalker) to schedule recurring work at particular times or dates.

How to use?####

gem 'clockwork'

Run bundle install.
In clock.rb

include Clockwork

every(30.seconds, 'Send notifications'){
    `rake scheduler:send_notification`
}
every(1.day, 'Send Reminder', :at=>"00:00") {
	`rake scheduler:send_reminder`
}

Then run it with the clockwork binary:

$clockwork clock.rb
Starting clock for 2 events: [Send notifications Send Reminder]

Depends on your application requirement and scale, you could choose any of these popular gems to schedule your script. For my application, it doesn't require precise time. So even though it's event_date dependent, I could just run the job every 2 hours and send out reminders if needed. Rufus fits my case very well.