Scheduling tasks is so common nowadays, I wonder how long it will take until we see it baked in right in Ruby on Rails. Until then whenever is the gem that would do the job for us. whenever schedules the tasks by creating cron jobs on our server, adding some nice scoping too.

Installation

We can install the gem directly:

sh
gem install whenever

or by adding it to the Gemfile of our app:

sh
gem 'whenever', :require => false

and running the bundle command:

sh
bundle update

Next we run the generator:

sh
wheneverize .

This command will add a schedule.rb file inside the config directory of our Rails app. It is here where we define the cron jobs using a lovely ruby DSL, rather the quite enigmatic cron syntax. Using whenever, we are sure that the job is defined correctly, and we’re not running a daily job every month ;).

The schedule.rb has already some examples to guide us:

ruby
every 2.hours do
  command "/usr/bin/some_great_command"
  runner "MyModel.some_method"
  rake "some:great:rake:task"
end

every 4.days do
  runner "AnotherModel.prune_old_records"
end

We are going to schedule a rake task, which we’ll create next.

Schedule your task

Our rake task will be very simple and most futile:

ruby
namespace :tutor do
  desc 'A simple task'
  task :simple => :environment do
    puts 'I am a simple task'
  end
end

Let’s now update the config/schedule.rb file to run the task every 24 hours:

ruby
every 24.hours do
  rake "tutor:simple"
end

Plain English, right?!

Integrate with Capistrano

Now we need to the schedule to our server. We’re deploying with Capistrano 3, so we’ll see how we can integrate whenerver with it.

The github Readme suggests to add the below line to our Capfile:

ruby
require "whenever/capistrano"

but this did not work well for me. Please try it, and if it works for you, you can ignore the rest of this section.

We can create our own Capistrano tasks. Let’s add the following block to config/deploy/production.rb (or any other file with our Capistrano tasks).

ruby
namespace :deploy do
  desc "Update crontab with whenever"
  task :update_cron do
    on roles(:app) do
      within current_path do
        execute :bundle, :exec, "whenever --update-crontab #{fetch(:application)}"
      end
    end
  end

  after :finishing, 'deploy:update_cron'
end

Here we call the whenever command for our application, and tell the Capistrano to run at the end of the deployment process.

The cron file

Let’s run the Capistrano task directly to see how it has changed the cron file:

sh
cap production deploy:update_cron

If we login to our server and run:

sh
crontab -l

we should see the following lines:

sh
# Begin Whenever generated tasks for: tutor
0 0 * * * /bin/bash -l -c 'cd /home/webadmin/apps/tutor/releases/20150418102048 && RAILS_ENV=production bundle exec rake tutor:simple --silent'

# End Whenever generated tasks for: tutor

Whenever uses /bin/bash -l -c to run the commands on the server. We can clearly see that the command when run, will go to the release directory of our tutor app, and will execute the rake task with bundle in a production environment. Which is what we want. For clarity, whenever has also added some scoping comments.

Happy coding!