June 06, 2010

Rake Tasks 101

I’ve been working with Rake quite a bit on my current project so I thought I’d share some beginner tips.

Before I go into Rake, what is it? Rake is a Ruby-based build program. Ruby on Rails uses Rake quite a bit in it’s process. If you’ve worked on a Rails project you’ll used one, some or all of the following: rake db:create, rake gems:unpack, rake db:migrate, and rake test. Now that’s not all of Rail’s Rake tasks, just some common ones.

You’re here to make your own Rake tasks so lets get started!

Make Your Task

Rake files can live within plugins or in your Rails.root/lib/tasks directory. In this post I’ll be referencing the latter. Let’s create our new rake file: Rails.root/lib/tasks/manners.rake

Next we can declare our Rake task in the manners.rake file:

1
2
3
4
5
6
namespace :manners do
  desc 'the description of what my rake task will do'
  task :greet do
    puts 'Hello from Rake'
  end
end

After thats done we should be able to execute our new task at the command-line with rake manners:greet.

Task Variables

Let’s make the Rake task reusable. To do this we’ll pass in some variables. Rewrite the task block of the manners.rake file like this:

1
2
3
4
task :greet, :name do |cmd, args|
  puts command: #{cmd}”
  puts args: #{args.inspect}”
end

The output you receive from running the task again should look like this:

1
2
command: manners:greet
args: {}

Now run the task again, but define the variable by calling it like this, rake manners:greet[Dane]. That should yield:

1
2
command: manners:greet
args: {:name => “Dane”}

This is where Rake tasks get interesting. Rake always passes in the command run into the task block as the first variable, thats why we see “command: manners:greet.” The second variable defines the hash that will contain all the variables passed into the the Rake task. The args hash index is any symbol that follows the task name symbol. We set :greet as the task name so :name becomes an available index in the args hash.

Lets rework the :greet task a little bit by defining our variables as :first_name and :last_name.

1
2
3
task :greet, :first_name, :last_name do |cmd, args|
  puts Good day #{args[:first_name]} #{args[:last_name]}”
end

Run the Rake task and define both variables, rake manners:greet[Dane,Harrigan].

Spaces are not allowed when passing variables into a Rake task when calling it at the command-line. This is why its written as [Dane,Harrigan] and not [Dane, Harrigan]. Quotes can be used if spaces are necessary to a variable for example, rake manners:greet['Mr. Dane',Harrigan].

Rake Dependencies

We’ve built a Rake task so now lets make another and have it depend on manners:greet. Add a manners:question task that asks, “How are you?” Start by just making a Rake task like we did with manners:greet. To make the :question task dependent on :greet define the task as task :question => 'manners:greet'. Our manners.rake file should look like this:

1
2
3
4
5
6
7
8
9
10
11
namespace :manners do
  desc 'Greet the Rake user'
  task :greet, :first_name, :last_name do |cmd, args|
    puts Good day #{args[:first_name]} #{args[:last_name]}”
  end

  desc 'Ask a question'
  task :question => 'manners:greet' do
    puts 'How are you doing?'
  end
end

If we run rake manners:question you’ll see that it greets us with, “Good day,” and, “How are you doing?” but we can’t set variables in a task dependency when its declared this way. Defining the dependency this way doesn’t work either, task :question => 'manners:greet[Dane,Harrigan]'. Let’s remove the ‘manners:greet’ dependency and call invoke on it instead.

1
2
3
4
task :question do
  Rake::Tasks['manners:greet'].invoke('Dane','Harrigan')
  puts 'How are you doing?'
end

Now when we run the task you’ll see a greeting to Dane and the question.

Tasks Run Once

When calling a task with invoke or execute Rake keeps track of whether or not it has already run. If the task has run already it wont run a second time. If we did the following you’ll only see one greeting.

1
2
3
4
5
task :question do
  Rake::Task['manners:greet'].invoke('Dane','Harrigan')
  Rake::Task['manners:greet'].invoke('John','Smith')
  puts 'How are you doing?'
end

You won’t see the greeting to John Smith. Well that’s rude, but we can fix this easily. If you want to call the task multiple times you’ll need to reenable the task each time before calling it. You can reenable a task anywhere, but I’ve found it makes the most sense to call reenable at the end of the task block of the one being reenabled. In our example we’ll call reenable inside of task :greet.

1
2
3
4
task :greet, :first_name, :last_name do |cmd, args|
  puts Good day #{args[:first_name]} #{args[:last_name]}”
  Rake::Task['manners:greet'].reenable
end

Now if we call rake manners:question we’ll see both greetings. Perfect!

And We’re Done

Rake is a very nice piece of software and I encourage others to read up on it. I hope this post gave you enough understanding to start writing your own tasks. Also, please do comment if there are questions or other areas of Rake you’d like to know about. A Rake Tasks 102 post perhaps?

Rake Tasks 102 is up! If you liked Rake Tasks 101, I think you’ll enjoy 102 just as much.

14 Comments to “Rake Tasks 101”

  1. June 07, 2010

    Gokul Janga

    Thanks for the article. Not enough stuff out there about Rake. Maybe you could talk about accessing the Rails stack (i.e., models etc) inside the rake tasks?

  2. June 07, 2010

    Good stuff. It might be useful to say a bit about the different technologies of job processing (Rake, cron, background jobs), and how they relate.

    FWIW, I wrote a set of notes on using Rake outside of Rails here:

    http://www.stuartellis.eu/articles/rake/

  3. June 07, 2010

    @Gokul I completely agree. There are very few articles on Rake. I hope this post gets some people running that are new to it.

    @Stuart, Gokul Your ideas for another Rake post are great! I’m planning to put both of your ideas together for the follow up article. Thanks!

  4. June 08, 2010

    It may be worth noting that dependencies within the same namespace don’t need qualification:

    task :question => 'manners:greet' do

    can also be written

    task :question => 'greet' do

    or

    task :question => :greet do

    (Actually, I wasn’t aware that you could express the dependency as a string, which is a useful thing for the future).

    I think the more “traditional”, make-type uses for Rake where dependencies are expressed in terms of files, are less well-known and seldom documented.

  5. June 08, 2010

    @Mike Great point on passing the dependency as a symbol opposed to a string. For those reading this far, he is correct that in my example I didn’t need to prefix the :greet task with ‘manners.’ Although, when calling a dependency that isn’t within the same namespace, it is necessary. For example, if ‘db:migrate’ was a dependency of :greet or :question, it would need to be passed as a string.

  6. June 18, 2010

    Myron Marston

    You mention that with execute and invoke, Rake will only run the task once, unless you re-enable it. This is true of invoke, but with execute you can run the task as many times as you want:

    https://gist.github.com/f87177b29621afbaf682

  7. June 19, 2010

    @Myron Thanks for catching that! You’re right, execute can be called multiple times on a task. The difference between execute and invoke is invoke will run the task’s dependencies, but execute will not. For example, when the :question task was set up like as “task :question => ‘manners:greet’” calling execute on :question would not run ‘manners:greet’.

  8. June 23, 2010

    The second code example under “Rake Dependencies” has an error:

    Rake::Tasks[:greet].invoke(‘Dane’,’Harrigan’)

    should be:

    Rake::Task[‘manners:greet’].invoke(‘Dane’,’Harrigan’)

    Thanks for a good article!

  9. June 24, 2010

    @Michael Fixed! Thanks for the extra pair of eyes!

  10. August 02, 2010

    kawun

    Hello,

    Is it possible to declare task dependency like

    task :foo => :bar do |cmd, args| end

    and also have access to passed arguments?

    I just don’t like to call dependency directly from the task body.

  11. August 02, 2010

    kawun

    Ok, I found answer to my question. Here is the code:

    task :foo, :name, :needs => :bar do |cmd, args| end

  12. August 05, 2010

    Btw. how often these Rake Task Variables are used?

    They seem pretty cumbersome, and I guess people often just use env vars like

    RAILS_ENV=production rake db:migrate

  13. August 09, 2010

    @Kawun sorry for the late reply. You’re correct, a rake task will look for :needs to be declared if variables are declared as well. And thank you for sharing your answer!

  14. August 09, 2010

    @Evgenily I haven’t seen many people used task variables. Environmental variables are used often in Rails for things like setting the Rails environment (your example) and setting version numbers (rake db:migrate VERSION=0).

    A perk to passing variables at the environment level is they’ll be available to every task and dependency through ARGV, but the con to this is rake isn’t aware of these so it wont educate the user when they do rake -T.

    I’ve used rake variables in my projects when I could, but it depends on the scenario.

Leave a Comment


Please wait...