There’s a nice Railscast introduction to rake for Rails, which goes into a number of other important details that aren’t covered in this post. Below is a little tutorial of creating a Rails rake task and getting it to run remotely on heroku.
Introduction to Rake
In lib/tasks, create a file called greet.rake
task :greet do puts "Hello world" end
By naming the task .rake and putting it in this special place rails will automatically pick it up and make it available to you. You can see it listed if you type: rake -T on the command line. To run it:
rake greet
which will print “Hello world”
to run one task before another, specify a dependency like this (multiple tasks may be specified in the same file):
task :ask => :greet do puts "How are you?" end
Writing a Practical Rake Task
Now for the task at hand, I’m going to create a rake task which creates a bunch of fake data for me to test with. First I’ll create a little experimental app:
rails rake_example cd rake example script/generate scaffold person first_name:string last_name:string rake db:migrate rails generate admin fake_people
Here’s the rake task (lib/tasks/fake_people.rake):
require 'faker' namespace :admin do desc "create some fake data" task :fake_people => :environment do print "How many fake people do you want?" num_people = $stdin.gets.to_i num_people.times do Person.create(:first_name => Faker::Name.first_name, :last_name => Faker::Name.last_name) end print "#{num_people} created.n" end end
Note that I’m using the faker gem (docs here) and I created a task dependency on loading the rails environment so I could access my Person model.
Now I can run
rake admin:fake_people
and it will prompt me to ask how many I want and then it will create them. Cool goodness, yes?
Running Remotely on Heroku
We’re not done yet. I want to deploy this on heroku and be able to run the task remotely. For this, there are two gotchas, first I can’t run an interactive script remotely; also I need to tell heroku that I am using the fake gem and make sure it is installed.
1) removing interactivity
Instead of an interactive script, we can set an environment variable or command line argument (thanks to a tip by Adam Wiggins).
My modified task looks like this:
require 'faker' namespace :admin do desc "create some fake data" task :fake_people => :environment do num_people = ENV['NUM_RECORDS'].to_i num_people.times do Person.create(:first_name => Faker::Name.first_name, :last_name => Faker::Name.last_name) end print "#{num_people} created.n" end end
which I can call locally from the command line like this:
rake admin:fake_people NUM_RECORDS=1
2) adding gem to heroku
I need to create a gems manifest, which sounds fancy, but is simply creating a .gems file at the root of my app with contents similar to what I would put in my config environment.rb to specify that my app requires a gem:
faker --version ">=0.3.1"
3) Deploy and Run
So I can deploy my app to heroku with the usual steps
git init git add . git commit -m "example app for rake script testing" heroku create git push heroku master heroku rake db:migrate
and run the task remotely:
heroku run rake admin:fake_people NUM_RECORDS=1
If you want your greet task to appear when you do ‘rake -T’ you need to add a description.
Also if you envision doing ‘rake gems:install’ at any point to you should move ‘require faker’ into the body of a task. Otherwise if you run ‘rake gems:install’ without faker installed, rake will fail. Here you could put it as the first line of your ‘fake_people’ task but you could also create a ‘require_faker’ task that did the require and was a dependency of ‘require_people’.
This is a very helpful post, thank you for writing it!
Are there any significant updates to the above methodology you are aware of for Rails 3?
Thank you for your post…
Could you tell me the significance of :environment in :task_name => :environment. what happens if i vomit this :environment ?
:task_name => :environment will load the Rails environment, which will allow you to access things like your ActiveRecord models or Rails config. Without that you can just run pure Ruby code.
The latest version of Heroku add “run” to the command. Therefore:
%> heroku run rake:db:migrate
instead of:
%> heroku rake:db:migrate
sorry my comment was it should be:
heroku run rake db:migrate
(replace colon with a space between rake and db)
Thanks!