Using the Whenever Gem in Your Rails App

There comes a point in every young Rails developer's career when she has to schedule a task to run periodically in her app. You can do this by hand in the crontab in the server, but the syntax is a little complicated. The Whenever gem is a wonderful gem that allows you to schedule your tasks in plain old Ruby, and updates your crontab automatically on a Capistrano deploy. I love this gem because I don't have to worry about remembering to update my crontab by hand, and I like that it documents my scheduled tasks right in my app, so other developers coming on board will quickly be able to see that there are tasks scheduled (whereas it's not as obvious if those tasks are only documented in the crontab). Ok, let's learn how to use Whenever!

Get the whenever gem

Get the most recent version of the Whenever gem (docs here).

Add it to your gemfile, along with :require => false.

gem 'whenever', '~> 0.9.4', :require => false

Including :require => false means that the gem will be installed, but won't be loaded into a process unless you explicitly call require "whenever". We're going to do that later when we talk about deploying. This is done for performance reasons, when you're using a gem like Whenever, that you need to run from the command line but don't otherwise use inside your Rails app.


Install the gem with bundler.

$ bundle

Schedule your tasks

To integrate Whenever into your project, cd into your main app directory and run the wheneverize command. This will create a schedule.rb file in the config directory of your Rails application. The schedule.rb file is where we'll write our tasks.

$ wheneverize .


In schedule.rb, create your scheduled tasks. The syntax here is really basic - check out these examples:

  • every 20.minutes do
  • every :hour do
  • every :friday, :at => '11pm' do
  • every :weekday do


This is one of the tasks I added to my schedule.rb file.

every 1.day, :at => '4:00 am' do
  runner "PeriodicRun.new.index_citrix_ids"
end

Debugging/development tips

When I first fill out schedule.rb, I usually run my tasks every 5 minutes, no matter how often I really want them run in production, just for debugging purposes. Once I can see that they are being run, I update schedule.rb with the correct interval.

I also like to put a logger in the method that I'm calling in the task, so I’ll know when it’s been run. This especially helps when I am debugging something.

  def index_citrix_ids
    some_imaginary_code_whee!
    Rails.logger.info("CitrixIndex updated at #{Time.now}")
  end

What if I only want to run jobs in a certain environment?

If you want to limit which environments to run a task in, use the @environment variable. In my app, I wanted the daily report email to only send in production, and not staging.

if @environment == "production"
    every :weekday, :at => '10pm' do
        rake "cronjobs:daily_log_email"
    end
end

Write your tasks to the crontab

After I code my tasks into my schedule file, I like to preview how Whenever will write it onto my crontab. Running the whenever command will show you a preview of the scheduled tasks, in cron format. It will NOT update your crontab, guys, FYI. This is what it looks like when you run whenever.

$ whenever

$ 0 4 * * * /bin/bash -l -c 'cd /Users/kathyheffern/code/myproject && bin/rails runner -e production '\''PeriodicRun.index_citrix_ids'\'''

## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated.
## [message] Run `whenever --help' for more options.


When you're looking at the task, converted into cron format, it's helpful to understand a little bit about cron syntax so you know what the asterisks and numbers represent.

# * * * * *  command to execute
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ └───── day of week (0 - 6)
# │ │ │ └────────── month (1 - 12)
# │ │ └─────────────── day of month (1 - 31)
# │ └──────────────────── hour (0 - 23)
# └───────────────────────── min (0 - 59)


When you're ready to update your crontab, there are two ways you can do it: by hand, or automatically, during a Capistano deploy. If you want to do it by hand, this is the command.

$ whenever --update-crontab


After you run that, you should see:

[write] crontab file updated


If you want to check that Whenever wrote your tasks correctly on the crontab, you can run the crontab -l command.

$ crontab -l


The result will look something like this:

# Begin Whenever generated tasks for: /Users/kathyheffern/code/myproject/config/schedule.rb

0 4 * * * /bin/bash -l -c 'cd /Users/kathyheffern/code/myproject && bin/rails runner -e production '\''PeriodicRun.index_citrix_ids'\'''

# End Whenever generated tasks for: /Users/kathyheffern/code/myproject/config/schedule.rb

Integrate with Capistrano 3

We use Capistrano 3 for deployment, so the following instructions are for Capistrano 3. If you're using an earlier version of Capistrano, the workflow is a little different. Refer to the Whenever readme for instructions.

In your Capfile, add this line:

require “whenever/capistrano”


Make sure that you require whenever after bundler in your Capfile. I originally had require “whenever/capistrano” in a line before require “whenever/bundler” and I got deployment errors.

In deploy.rb, set whenever_roles.

set :whenever_roles, ->{ [:web, :app]}


If you don't set them, whenever_roles will default to :db. Make sure you add the roles of the servers you want to have your tasks on to the list. Our servers' roles are set to :web and :app so I had to add both of those to my whenever_roles. We don't have any servers with a :db role in this project, so I didn't need :db on my whenever_roles. Now by default, your tasks will be deployed to any servers with the roles you've listed in whenever_roles. If you only need certain tasks on certain servers, you can specify them like this:

every :hour, :roles => [:db] do
  rake 'db:task' # will only be added to crontabs of :db servers
end


Now Whenever will update your crontab with the tasks you listed in schedule.rb every time you deploy.

After I deploy, I like to make sure the crontab was written correctly. You can do this by going onto the server and running the crontab -l command.

$ crontab -l

There you go! Let's get out there and schedule some tasks!

Hire Us