Schedule Cron Jobs with the Whenever Gem

Deepak Kumar
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/

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

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

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'

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'

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'

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.

Frequently Asked Questions (FAQs) about Scheduling Cron Jobs with the Whenever Gem

How do I install the Whenever gem in my Rails application?

To install the Whenever gem in your Rails application, you need to add it to your Gemfile. Open your Gemfile and add the following line: gem 'whenever', require: false. After adding this line, run bundle install in your terminal to install the gem. The require: false part is necessary because you don’t want the gem to be loaded when your application starts. Instead, it will be loaded when it’s needed.

How do I schedule a task using the Whenever gem?

To schedule a task using the Whenever gem, you need to create a schedule file. You can do this by running wheneverize . in your terminal. This will create a file named schedule.rb in the config directory of your Rails application. In this file, you can define your tasks and when they should run. For example, if you want to run a task every day at 5 PM, you would write every, at: '5:00 PM' do runner "MyModel.my_method" end.

Can I use Whenever to schedule tasks in a non-Rails Ruby application?

Yes, you can use the Whenever gem to schedule tasks in a non-Rails Ruby application. The process is similar to scheduling tasks in a Rails application. You need to install the gem, create a schedule file, and define your tasks in this file. However, instead of using the runner command to run your tasks, you would use the command command and provide the full path to the Ruby script you want to run.

How do I update my crontab with my Whenever schedule?

To update your crontab with your Whenever schedule, you need to run whenever --update-crontab in your terminal. This will write your Whenever schedule to your crontab. If you want to clear your crontab of your Whenever schedule, you can run whenever --clear-crontab.

Can I schedule tasks to run at specific times with Whenever?

Yes, you can schedule tasks to run at specific times with Whenever. In your schedule file, you can use the at option to specify the time when a task should run. For example, if you want a task to run every day at 5 PM, you would write every, at: '5:00 PM' do runner "MyModel.my_method" end.

How do I test my Whenever schedule?

To test your Whenever schedule, you can output it to your terminal by running whenever without any options. This will print your schedule in crontab format. You can also run your tasks manually to make sure they work as expected.

Can I schedule tasks to run at intervals with Whenever?

Yes, you can schedule tasks to run at intervals with Whenever. In your schedule file, you can use the every command to specify the interval at which a task should run. For example, if you want a task to run every 5 minutes, you would write every 5.minutes do runner "MyModel.my_method" end.

How do I handle errors in my Whenever tasks?

To handle errors in your Whenever tasks, you can use standard Ruby error handling techniques. For example, you can use a begin/rescue block to catch and handle exceptions. If an error occurs in a task, it will be logged to the standard error output (stderr), which you can redirect to a file for later analysis.

Can I use Whenever with other Ruby versions or implementations?

Yes, you can use Whenever with other Ruby versions or implementations. The Whenever gem is compatible with all Ruby versions from 1.8.7 onwards and with all major Ruby implementations, including JRuby and Rubinius.

Can I use Whenever in a multi-environment setup?

Yes, you can use Whenever in a multi-environment setup. In your schedule file, you can use the set command to specify different schedules for different environments. For example, you could have a different schedule for your development, staging, and production environments.