Using the Whenever gem to manage scheduled cron jobs without installing it on the server

September 30, 2009

I've been using Javan's Whenever gem to manage scheduled jobs in my project and its fantastic!! There are many existing resources where you can learn more (readme, railscast google group) but I'd like to describe the specific way I'm using it

  • When the gem is not installed on my server

  • How administrators can use Capistrano to both schedule and unschedule your jobs

  • How to use a library such as the Oracle client that requires certain environment variables

At the end of the day we want to have 2 capistrano tasks we can run to have cron call a rake task of ours on the schedule we want.

  cap schedule_jobs
  cap unschedule_jobs

If we want to get fancy and pass a custom configuration argument that will be passed into the rake task.

  cap schedule_jobs SOME_CONFIGURATION=false

When the gem is not installed on my server

Let's start with the Capfile

desc "Schedule the jobs"
task :schedule_job, :roles => :app, :only => { :primary => true } do
  some_configuration = ENV['SOME_CONFIGURATION'] || true #default to true
  arguments = ["RAILS_ENV=#{rails_env}",
               "SOME_CONFIGURATION=#{some_configuration}"].join(' ')
  run "cd #{current_path} && #{rake} whenever:update_crontab #{arguments}"

desc "Unschedule the jobs"
task :unschedule_job, :roles => :app, :only => { :primary => true } do
  run "cd #{current_path} && #{rake} whenever:update_crontab  UNSCHEDULE=true"

How does this differ from the example on the whenever site? Since the gem is not installed on the server we cannot call whenever from the command line so invoke the whenever:update_crontab rake task instead and to allow administrators to easily disable the scheduled jobs we define the unschedule_jobs capistrano task. Let's take a look at the whenever:update_crontab Rake task that gets this all done.

namespace :whenever do
  desc "updates crontab with our scheduled jobs"  
  task :update_crontab => :load_whenever_gem do     
    Whenever::CommandLine.execute({:update=>true, :identifier=>'YOUR_APP_NAME'})

  task :load_whenever_gem do     
      gem_dir_root = "#{RAILS_ROOT}/vendor/gems/"
      chronic_gem_dir = Dir["#{RAILS_ROOT}/vendor/gems/*"].detect do |subdir|
        subdir.gsub(gem_dir_root,"") =~ /^(\w+-)?chronic-(\d+)/ && File.exist?("#{subdir}/lib/chronic.rb")
      require "#{chronic_gem_dir}/lib/chronic"

      whenever_gem_dir = Dir["#{RAILS_ROOT}/vendor/gems/*"].detect do |subdir|
        subdir.gsub(gem_dir_root,"") =~ /^(\w+-)?whenever-(\d+)/ && File.exist?("#{subdir}/lib/whenever.rb")
      require "#{whenever_gem_dir}/lib/whenever"

    rescue MissingSourceFile => e
      raise "Cannot find Whenever or Chronic : #{e}"

The actual whenever:update_crontab task just does the same the command line does but unless you add config.gem 'whenever' to your environment (which I don't since whenever is not needed by my app at runtime) we also have the other task that loads whenever and chronic from the vendor/gems directory.

At this point we've gotten Capistrano calling a rake task to invoke whenever even though the gem is localized in my application but not installed on the server.

How administrators can use capistrano to both schedule and unschedule your jobs

The whenever gem does not have any support for unscheduling but it will schedule whatever is included in your schedule.rb file so if that file tells it to schedule nothing that's the same as unscheduling. Its easy to do that by wrapping the entire file with unless ENV['UNSCHEDULE'] (remember the UNSCHEDULE parameter we passed in the Capfile?)


  module MyApp
    module Job
      class CronRakeTask < Whenever::Job::Default
        def output
          "cd #{@path} && /usr/bin/env /usr/local/bin/cron_rake #{task} SOME_CONFIGURATION=#{ENV['SOME_CONFIGURATION']} RAILS_ENV=#{@environment}"

  set :path,        ENV['APP_PATH'] || RAILS_ROOT 
  set :environment, RAILS_ENV || 'production'
  every, :at => '10:00pm' do
    command 'do_something', :class       => MyApp::Job::CronRakeTask, 
                            :environment => @environment, 
                            :path        => @path

What's going on with the weird CronRakeTask? This brings us to the final point

How to use a library such as the Oracle client that requires certain environment variables

Cron loads its environment settings differently than an interactive shell and typically does not have all the environment variables you may have in your profile file. You could solve this by adding them to the top of your crontab file but I prefer to leave that file as simple as possible and create a wrapper script to call instead of rake. Basically the CronRakeTask does the same as Whenever's built in RakeTask except it calls /usr/local/bin/cron_rake instead of rake.

The cron_rake file just sets the environment variables I need then calls rake.


export PATH=/usr/local/lib/ruby-enterprise/bin:$PATH
export ORACLE_HOME=/opt/oracle

rake $@

As I said in the beginning since I've been using the Whenever gem I no longer need to manually edit my crontab files ever and I can enable my jobs as part of my normal deployment process. Its wonderful and I think everyone should use it!