views:

7237

answers:

9

What's the best way to run scheduled tasks in a Rails environment? Script/runner? Rake?

+7  A: 

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 : 1) you have full access to all your Rails objects just as in a normal controller. 2) you can develop and test just as you do normal actions. 3) you can also invoke your tasks adhoc from a simple web page. 4) You don't consume any more memory by firing up additional ruby/rails processes.

Freakent
How to prevent others from access this task ? If the task taking cpu and called it frequently will cause problems.
art
Same way you would for any other controller. Make sure you implement some sort of security - AuthLogic or restful_authentication, then add role management on top of that with acl9 or something. You can then you your rails app to serve all tasks via restful interface with security and stuff, just like any other web service. This gives things like: you can use your Domain to authenticate and let "server" user run tasks that are scheduled in cron or Scheduled Tasks if you use windows. Also this allows you to access same things throgh script/runner if you need to. Continued in the next comment.
Nick Gorbikoff
Ran out of room above. The main advantage for me is that I'm the only admin level user at my work and I don't wont non-programmers/non-system admins with root access to kick of certain jobs. This setup lets you create a page in your rails app, where an authorized user (such as Office Manager or Inventory Manager) can log in and kick of certain jobs, without giving them access to script/runner or a cron job. And again if you go this route you can later use some other rails plugin or gem such as workling or background ( don't remeber exact names, see ruby-toolbox.com)
Nick Gorbikoff
+2  A: 

I use backgroundrb.

http://backgroundrb.rubyforge.org/

I use it to run scheduled tasks as well as tasks that take too long for the normal client/server relationship.

salt.racer
+10  A: 

Recently Railscasts.com deployed some very interesting casts about this. They cover many cases. Watch the episodes and decide by yourself.

Episode 127 - Rake in background

Episode 128 - Starling and workling (This one was made for you)

Episode 129 - Custom Daemon

Ricardo Acras
A: 

I'm not really sure, I guess it depends on the task: how ofte, how complicated, how much direct communication with the rails project is needed etc. I guess if there was just "One Best Way" to do something, there wouldn't be so many different ways to do it... ;P

At my last job in a Rails project, we needed to make a batch invitation mailer (survey invitations, not spamming ;)), wich should send the planned mails whenever the server had time. I think we was going to use daemontools to run the rake tasks I had created.

Unfortunately, our company had some money problems and was "bought" by the main rival so the project was never completed, so I don't know what we would eventually have used.

Stein G. Strindhaug
+3  A: 

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 ;-)

webmat
+2  A: 

Both will work fine. I usually use script/runner.

Here's an example:

0 6 * * * cd /var/www/apps/your_app/current; ./script/runner --environment production 'EmailSubscription.send_email_subscriptions' >> /var/www/apps/your_app/shared/log/send_email_subscriptions.log 2>&1

You can also write a pure-Ruby script to do this if you load the right config files to connect to your database.

One thing to keep in mind if memory is precious is that script/runner (or a Rake task that depends on 'environment') will load the entire Rails environment. If you only need to insert some records into the database, this will use memory you don't really have to. If you write your own script, you can avoid this. I haven't actually needed to do this yet, but I am considering it.

Luke Francl
+5  A: 

Use Craken (rake centric cron jobs)

Thibaut Barrère
+1  A: 

Here's how I have setup my cron tasks. I have one to make daily backups of SQL database (using rake) and another to expire cache once a month. Any output is logged in a file log/cron_log. My crontab looks like this:

crontab -l # command to print all cron tasks
crontab -e # command to edit/add cron tasks

# Contents of crontab
0 1 * * * cd /home/lenart/izziv. whiskas.si/current; /bin/sh cron_tasks >> log/cron_log 2>&1
0 0 1 * * cd /home/lenart/izziv.whiskas.si/current; /usr/bin/env /usr/local/bin/ruby script/runner -e production lib/monthly_cron.rb >> log/cron_log 2>&1

The first cron task makes daily db backups. The contents of cron_tasks are the following:

/usr/local/bin/rake db:backup RAILS_ENV=production; date; echo "END OF OUTPUT ----";

The second task was setup later and uses script/runner to expire cache once a month (lib/monthly_cron.rb):

#!/usr/local/bin/ruby
# Expire challenge cache
Challenge.force_expire_cache
puts "Expired cache for Challenges (Challenge.force_expire_cache) #{Time.now}"

I guess I could backup database some other way but so far it works for me :)

The paths to rake and ruby can vary on different servers. You can see where they are by using:

whereis ruby # -> ruby: /usr/local/bin/ruby
whereis rake # -> rake: /usr/local/bin/rake
+6  A: 

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.

tardate