Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A cron job for rails: best practices?

I've used the extremely popular Whenever on projects that rely heavily on scheduled tasks, and it's great. It gives you a nice DSL to define your scheduled tasks instead of having to deal with crontab format. From the README:

Whenever is a Ruby gem that provides a clear syntax for writing and deploying cron jobs.

Example from the README:

every 3.hours do
  runner "MyModel.some_process"       
  rake "my:rake:task"                 
  command "/usr/bin/my_great_command"
end

every 1.day, :at => '4:30 am' do 
  runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
end

I'm using the rake approach (as supported by heroku)

With a file called lib/tasks/cron.rake ..

task :cron => :environment do
  puts "Pulling new requests..."
  EdiListener.process_new_messages
  puts "done."
end

To execute from the command line, this is just "rake cron". This command can then be put on the operating system cron/task scheduler as desired.

Update this is quite an old question and answer! Some new info:

  • the heroku cron service I referenced has since been replaced by Heroku Scheduler
  • for frequent tasks (esp. where you want to avoid the Rails environment startup cost) my preferred approach is to use system cron to call a script that will either (a) poke a secure/private webhook API to invoke the required task in the background or (b) directly enqueue a task on your queuing system of choice

In our project we first used whenever gem, but confronted some problems.

We then switched to RUFUS SCHEDULER gem, which turned out to be very easy and reliable for scheduling tasks in Rails.

We have used it for sending weekly & daily mails, and even for running some periodic rake tasks or any method.

The code used in this is like:

    require 'rufus-scheduler'

    scheduler = Rufus::Scheduler.new

    scheduler.in '10d' do
      # do something in 10 days
    end

    scheduler.at '2030/12/12 23:30:00' do
      # do something at a given point in time
    end

    scheduler.every '3h' do
      # do something every 3 hours
    end

    scheduler.cron '5 0 * * *' do
      # do something every day, five minutes after midnight
      # (see "man 5 crontab" in your terminal)
    end

To learn more: https://github.com/jmettraux/rufus-scheduler


Assuming your tasks don't take too long to complete, just create a new controller with an action for each task. Implement the logic of the task as controller code, Then set up a cronjob at the OS level that uses wget to invoke the URL of this controller and action at the appropriate time intervals. The advantages of this method are you:

  1. Have full access to all your Rails objects just as in a normal controller.
  2. Can develop and test just as you do normal actions.
  3. Can also invoke your tasks adhoc from a simple web page.
  4. Don't consume any more memory by firing up additional ruby/rails processes.

The problem with whenever (and cron) is that it reloads the rails environment every time it's executed, which is a real problem when your tasks are frequent or have a lot of initialization work to do. I have had issues in production because of this and must warn you.

Rufus scheduler does it for me ( https://github.com/jmettraux/rufus-scheduler )

When I have long jobs to run, I use it with delayed_job ( https://github.com/collectiveidea/delayed_job )

I hope this helps!


script/runner and rake tasks are perfectly fine to run as cron jobs.

Here's one very important thing you must remember when running cron jobs. They probably won't be called from the root directory of your app. This means all your requires for files (as opposed to libraries) should be done with the explicit path: e.g. File.dirname(__FILE__) + "/other_file". This also means you have to know how to explicitly call them from another directory :-)

Check if your code supports being run from another directory with

# from ~
/path/to/ruby /path/to/app/script/runner -e development "MyClass.class_method"
/path/to/ruby /path/to/rake -f /path/to/app/Rakefile rake:task RAILS_ENV=development

Also, cron jobs probably don't run as you, so don't depend on any shortcut you put in .bashrc. But that's just a standard cron tip ;-)