Rake & ruby: some basics to get you started

When developing using Ruby on Rails, you probably already used Rake. Rake is a simple ruby build program. Basically, rake offers somewhat of the same capabilities that make does.

Using rails, you probably already used tasks like db:migrate, db:seed, etc

In this post, I’ll try to cover the most used features of rake, or at least, the once I’ve used before.

Rake is primarily task based, where you write your logic into small tasks. If you are planning on writing a bigger command line tool, then Thor might be a better fit, since Thor removes the pain of parsing command line options.

So without further ado, let’s get into the basic of Rake. The following examples can be stored in a file with name Rakefile . The rake command will automatically look for a Rakefile within your current directory.

Namespaces

To organize your code better, rake supports the concept of namespaces. This essentially lets you group tasks inside a single namespace. In Rails, you might already seen namespaces like db:…, which groups all db tasks or the assets namespace:

namespace :universe do

end

namespace :universe do
  namespace :world do

  end
end

Creating a rake task

There is nothing difficult about creating a simple task in Rake. First, start off by writing a small description. The description is useful, because it gets displayed when you list all available tasks. In this example we output the simple “hello world” string when calling the shout task.

namespace :universe do
  namespace :world do
    desc 'shout it out'
    task :shout do
      puts "hello world"
    end
  end
end

Running rake tasks

To run a rake task, just call the rake command with the name of your task. Don’t forget to include your namespaces when you have them. Namespaces and tasks are separated using a semi-column:

$ rake universe:world:shout
hello world

To view all your avaiable tasks, just run the rake command and pass the T parameter:

$ rake -T
rake universe:world:shout # shout it out

Default rake tasks

Using rake, you can define a default task. This boils down to the fact that your default task will be run if you type rake without arguments. You can define a default task as follow:

task default: 'universe:world:shout'
$ rake
hello world

Dependencies

Needless to say that certain rake tasks can depend on the execution of others. You can define dependencies by defining your dependent tasks as an array:

namespace :universe do
  namespace :world do

  desc 'shout it out'
  task :shout do
    puts 'hello world'
  end

  desc 'greet the world'
    task greet: [:shout] do
      puts '** start waving **'
    end
  end
end

Now if you run the greet task, you get the following output:

$ rake universe:world:greet
hello world
** start waving **

As you can see, since you defined the shout task as a dependency, the shout task will be run before your greet task.

Invoking vs executing rake task

The beauty of rake is, that you can call tasks from within another task. Here you have 2 possibilities. You can ether call the task by using the Task#execute or Task#invoke method. Although both methods will run the called rake task, there is a difference between the two of them.

namespace :universe do
  namespace :world do

    desc 'shout it out'
    task :shout do
      puts 'hello world'
    end

    desc 'greet the world'
    task greet: [:shout] do
      puts '** start waving **'
    end

    desc 'invoke greeting'
    task :invoke_greeting do
      Rake::Task['universe:world:greet'].invoke
    end

    desc 'execute greeting'
    task :execute_greeting do 
      Rake::Task['universe:world:greet'].execute
    end
  end
end

Now lets call the invoke_build and execute_build tasks and see what happens:

$ rake universe:world:invoke_greeting
hello world
** start waving **

$ rake universe:world:execute_greeting
** start waving **

As you can see, the difference between invoke and execute is the execution of your dependencies. In the case where you call the execute method, your task will get executed, but not the dependency, while invoking it, will call the whole chain.

Passing parameters

You can pass parameters to the tasks by specifying formal arguments in rake by adding symbol arguments to the task.

These parameters can then be entered on

  • the command line when you call the task
  • passed between the parentheses of the invoke method
  • passed as en array of symbols if the task at hand is called as a dependency
namespace :universe do
  namespace :world do

    desc 'shout it out'
    task :shout, :name do |t, args|
      puts "hello #{args[:name]}"
    end

    desc 'greet the world'
    task :greet, [:name] => [:shout] do
      puts '** start waving **'
    end

    desc 'invoke greeting'
    task :invoke_greeting do
      Rake::Task['universe:world:greet'].invoke('Michael')
    end
  end
end
# pass it throug the invoke method
$ rake universe:world:invoke_greeting
hello Michael
** start waving **

# pass it through the command line directly to the shout task
$ rake universe:world:shout['Michael']
hello Michael

# pass it through the command line to the greet task that has the shout task as a 
# dependency
$ rake universe:world:greet['Michael']
hello Michael
** start waving **

Redefining rake tasks

Adding extra functionality to an existing task can be tricky. Here you could write an extension and pass it in as a dependency, but then it would be run before your primary task. If it should be run after the primary task, you can just set your extension after the primary task, with the same name.

namespace :universe do
  namespace :world do

    desc 'shout it out'
    task :shout, :name do |t, args|
      puts "hello #{args[:name]}"
    end

    desc 'greet the world'
    task :greet, [:name] => [:shout] do
      puts '** start waving **'
    end

    desc 'shout it out'
    task :greet, [:name] => [:shout] do
      puts '** stop waving **'
    end
  end
end
$ rake universe:world:greet['Michael']
hello Michael
** start waving **
** stop waving **

Now when you call the task, you’ll see that your redefinition will be run at the end. Of course, make sure you redefinition is defined after the primary one!

Run a rake task multiple times

When starting to automate stuff, your might come in a situation where you need to run a task more than once. The the following example:

namespace :universe do
  namespace :world do

    desc 'shout it out'
    task :shout, :name do |t, args|
      puts "hello #{args[:name]}"
    end

    desc 'greet the world'
    task :greet, [:name] => [:shout] do
      puts '** start waving **'
    end

    desc 'shout it out'
    task :greet, [:name] => [:shout] do
      puts '** stop waving **'
    end

    desc 'invoke greeting'
    task :invoke_greeting do
      %w(Michael Caroline).each do |person|
        Rake::Task['universe:world:greet'].invoke(person)
      end
    end
  end
end

When calling the invoke_greeting task you’ll see that the greet task is only run once:

$ rake universe:world:invoke_greeting
hello Michael
** start waving **
** stop waving **

When you wan’t to run it multiple times, you’ll need to re-enable it:

namespace :universe do
  namespace :world do

    desc 'shout it out'
    task :shout, :name do |t, args|
      puts "hello #{args[:name]}"
    end

    desc 'greet the world'
    task :greet, [:name] => [:shout] do
      puts '** start waving **'
    end

    desc 'shout it out'
    task :greet, [:name] => [:shout] do
      puts '** stop waving **'
    end

    desc 'invoke greeting'
    task :invoke_greeting do
      %w(Michael Caroline).each do |person|
        Rake::Task['universe:world:greet'].invoke(person)
        Rake::Task['universe:world:greet'].reenable
      end
    end
  end
end

run the task and see:

$ rake universe:world:invoke_greeting
hello Michael
** start waving **
** stop waving **
** start waving **
** stop waving **

Hmmm, something is not right, you see that the :greet task is now run twice, but the shout task is not. In fact, it keeps track of all tasks that are already run. So you need to re-enable the dependency as well:

namespace :universe do
  namespace :world do

    desc 'shout it out'
    task :shout, :name do |t, args|
      puts "hello #{args[:name]}"
    end

    desc 'greet the world'
    task :greet, [:name] => [:shout] do
      puts '** start waving **'
    end

    desc 'shout it out'
    task :greet, [:name] => [:shout] do
      puts '** stop waving **'
    end

    desc 'invoke greeting'
    task :invoke_greeting do
      %w(Michael Caroline).each do |person|
        Rake::Task['universe:world:greet'].invoke(person)
        Rake::Task['universe:world:greet'].reenable
        Rake::Task['universe:world:shout'].reenable
      end
    end
  end
end
$ rake universe:world:invoke_greeting
hello Michael
** start waving **
** stop waving **
hello Caroline
** start waving **
** stop waving **

These are some of the basic functionalities in Rake. But rake holds more advanced features, to much to sum up here.