Ruby
Article

Schedule Cron Jobs with the Whenever Gem

By Deepak Kumar

Reloj despertador FO

Sometimes, we have to run jobs at particular intervals to, say, backup logs, send emails, etc. In Unix systems, we can schedule such tasks using the built-in cron daemon. Cron is a daemon used to execute scheduled tasks. Unix systems provides a command, called crontab, that allows individual users to create scheduled tasks.

Cron searches its spool area (/var/spool/cron/crontabs) for crontab files and loads them into memory. Files in this directory should not be accessed directly – the crontab command should be used to access and update them. Use the following command to edit your own crontab file:

$ crontab -e

Suppose you want to backup your log directory every day at 1:10 AM. Add the following line to the crontab file:

10 1 * * * *  /path/to/your/backup/script.sh

Cron uses 10 1 * * * * to figure out the schedule for executing the job. To understand the format, here is the mapping for each field:

* * * * * *
| | | | | |
| | | | | +-- Year              (range: 1900-3000)
| | | | +---- Day of the Week   (range: 1-7, 1 standing for Monday)
| | | +------ Month of the Year (range: 1-12)
| | +-------- Day of the Month  (range: 1-31)
| +---------- Hour              (range: 0-23, 1 in our example)
+------------ Minute            (range: 0-59, 10 in our example)

An asterisk (*) tells cron to run this command for every entry in that range. In our example, there are asterisks for Day of Month, Month of Year, Day of Week, and Year. This means the command will be run every day of the month in every month of the year on every day of the week in every year.

Directly editing the crontab, however, is bit hard, and it’s painful to update the crontab. If you are in a Ruby/Rails project, there is a wonderful gem called whenever which can be used to add and schedule tasks to your crontab.

Let’s take an example of sending a daily digest email to users. In this case, we might have a mailer like this:

# file: app/mailers/user_mailer.rb

class UserMailer < ActionMailer::Base
  def digest_email_update(options)
    # ... email sending logic goes here
  end
end

Since we want to send the email as a separate process, let’s create a Rake task to fire off the email. Add a new file called email_tasks.rake to lib/tasks directory of your Rails application:

# file: lib/tasks/email_tasks.rake

desc 'send digest email'
task send_digest_email: :environment do
  # ... set options if any
  UserMailer.digest_email_update(options).deliver!
end

The send_digest_email: :environment means to load the Rails environment before running the task, so you can access the application classes (like UserMailer) within the task.

Now, running the command rake -T will list the newly created Rake task. Test everything works by running the task and checking whether the email is sent or not.

rake send_digest_email

At this point, we have a working rake task which can be scheduled using crontab.

The whenever Gem

Install the whenever gem by adding the following line to the Gemfile:

# file: Gemfile

gem 'whenever', require: false

Now, bundle up:

$ bundle

Go to the project directory and run the wheneverize command to create a schedule file:

$ wheneverize .

This will create a schedule.rb file in the config directory of your Rails application. Edit the schedule.rb file to schedule the task. Say we need to send the digest email at 10PM every day:

# file: config/schedule.rb

every :day, at: '10pm' do
  # specify the task name as a string
  rake 'send_digest_email'
end

Running the whenever command will output a preview of the generated schedules in the actual cron format:

$ whenever

0 22 * * * /bin/bash -l -c 'cd /home/deepak/Work/testapp && RAILS_ENV=production bundle exec rake send_digest_email --silent'

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

The first line of the output is the actual command to be run at 10PM daily. This command will be run by the cron daemon once it is been written to crontab.

You can verify the created schedule and then update (write) your crontab using:

$ whenever -w

[write] crontab file written

Which means that your crontab is updated, you can check it by opening the crontab file:

$ crontab -e

Here you will be asked to choose an editor of your choice, and then you will be dropped to that editor. You can see all the existing cronjobs for the current logged in user.

Run whenever --help to see various options available:

$ whenever --help

One of the useful option is -f this allow you to use a different schedule file

$ whenever -f /path/to/schedule/file/myschedule.rb

To clear your crontab, run the following command:

$ whenever -c

[write] crontab file

That command will clear your crontab file. This is useful when you have made an error or want to discontinue a cron job.

Custom Job Types

The whenever gem offers three other job types, outside of Rake tasks: command, runner, and rake. You can create your own job type, too. In the following example, I use all these three job types.

The runner job type allows you to input an executable piece of code as a string to be run at a particular interval. Similarly, command accepts a script to be run at an interval whereas. As you already know, rake runs a Rake task defined in your application.

every 3.hours do
  runner 'User.expire_session_cache'
  rake 'rake_task_name'
  command '/usr/bin/command_name'
end

Suppose you want to create a custom job_type. First, the new job type needs to be defined, like so:

job_type :foo, '/usr/local/bin/foo :task :filename'

Once defined, use this new job type as follows in schedule.rb:

every 2.hours do
  foo 'somecommand', filename: 'inputfilepath'
end

Now, the foo command will be run every 2 hours using /usr/local/bin/foo inputfilepath.

Automate with Capistrano

The whenever gem provides Capistrano recipes to automate updating the crontab file updating during deployment. It’s a simple matter of requiring the plugin in the deploy file:

# file: config/deploy.rb

require 'whenever/capistrano'

set :whenever_environment, defer { stage }
set :whenever_command, 'bundle exec whenever'

Thats it. Now on deployment, Capistrano will update your crontab.

Wrapping Up

The main advantages of using the whenever gem is that you don’t have to remember the tedious crontab syntax or update the crontab by yourself, which could be hazardous to the existing cronjobs in the system. Also, whenever uses a script file (like schedule.rb) which is just plain Ruby, so you can take advantage of Ruby’s friendly and readable syntax. Updating the deployment environment’s crontab file with Capistrano tasks is really nice, as well.

All in all, if you have jobs that need to run on a schedule, using the whenever gem allows you to leverage the power of Unix with the convenience of Ruby.

Happy Coding.

Comments
yumikohey

I want to know, if I schedule the job, and set environment = production, will it be able to update my server side's database without using an extra dyno?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Ruby, once a week, for free.