What's the best way to run scheduled tasks in a Rails environment? Script/runner? Rake?
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.
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.
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)
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.
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 ;-)
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.
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
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.