Scheduled jobs for your Rails app
November 10, 2011
If you need to run scheduled jobs in your Rails app (!= background jobs as in Resque), you should use the excellent whenever gem.
However, one detail that is really easy to miss is which shell environment the cron job actually runs under.
bundle: command not found
And an easy way to confuse yourself even further is by trying to debug issues in this area by:
- Logging into your server as the deployment user
- Doing
crontab -l
to see which entries whenever installed - Testing an entry by cut’n’pasting it to the shell prompt
On a project I work on, the staging and production machines are currently quite different. In production we use RVM (globally installed) while staging has rbenv locally installed for the deployment user.
So in staging I need to assume that no binaries are available and use the full path to bundler, in my case /home/passenger/.rbenv/versions/1.9.2-p290/bin/bundle
.
(I know, symlinking this to eg. /usr/local/bin/bundle
would probably be more elegant, but I actually like the reminder of where my ruby is actually installed. I don’t spend much time on the servers, so I tend to forget.)
Running stuff only on certain stages
EDIT: See Markham’s comment for a better way to do achieve the same result.
Another issue I struggled with for a while is how to schedule jobs only in production. For example, I don’t want to run S3 backups from staging.
I use capistrano/multistage
, so I needed to inspect the value of the current “stage” from schedule.rb
. It turns out that by including this:
require 'capistrano/ext/multistage' set :whenever_environment, defer { stage } require "whenever/capistrano"
…the stage is available in schedule.rb as @environment. Very simple, but it took some googling to find.
Example schedule.rb
Below is my anonymized schedule.rb. It should be pretty self explanatory, post questions in the comments if not.
def production? @environment == 'production' end set :bundler, production? ? "gp_bundle" : "/home/passenger/.rbenv/versions/1.9.2-p290/bin/bundle" job_type :gp_rake, "cd :path && RAILS_ENV=:environment :bundler exec rake :task --silent :output" job_type :gp_runner, "cd :path && :bundler exec rails runner -e :environment ':task' :output" job_type :gp_bundle_exec, "cd :path && RAILS_ENV=:environment :bundler exec :task" every 5.minutes do gp_rake "thinking_sphinx:index" end every 30.minutes do gp_runner "FooBar.update" end every 1.day, :at => "4:00am" do gp_runner "Baz.purge_inactive" end if production? every 1.day, :at => "4:00am" do gp_bundle_exec 'backup perform --trigger my_backup --config-file config/backup.rb --log-path log' end end
EDIT: Jakob Skjerning posted this snippet in the comments:
job_type :rake, “cd /var/www/appname/#{environment}/current && /home/appname/.rvm/gems/ree-1.8.7-2010.02@appname-#{environment}/bin/bundle exec /home/appname/.rvm/wrappers/ree-1.8.7-2010.02@appname-#{environment}/rake –silent RAILS_ENV=#{environment} :task :output”
November 10, 2011 at 1:03 pm
This is how our Whenever rake job type is defined in a client project:
job_type :rake, “cd /var/www/appname/#{environment}/current && /home/appname/.rvm/gems/ree-1.8.7-2010.02@appname-#{environment}/bin/bundle exec /home/appname/.rvm/wrappers/ree-1.8.7-2010.02@appname-#{environment}/rake –silent RAILS_ENV=#{environment} :task :output”
Not really nice, readable, or maintainable, but it works.
November 10, 2011 at 1:10 pm
Ah yes, I see how that could work…modulo WordPress formatting 😉
I’ll insert it into the post to preserve formatting. Nice trick.
September 11, 2012 at 12:34 am
I think a simpler way of handling the multistaging issue (in which you only want the cron job to run on your production machine) is to assign a unique role to your production machine (e.g. :prod), then specify that role for your whenever task(s):
every :day, ;at => ‘1am’, :roles => [:prod]
Would that work?
September 11, 2012 at 7:33 am
Ah yes, that definitely sounds like the right way to go. Thanks!