Sunday, October 19, 2008

Multiple rake tasks. Running rake tasks from a task

Rake is a very useful ruby build tool very similar to that of Ant for Java.

Sometimes you may want to run several rake tasks in succession and today I will go over several ways you can go about doing this.

Say we want to completely drop our existing database, recreate it and populate it with some sample data via a custom rake task called 'populate' (not shown here).

A naive way of accomplishing this would be to do the following:

 $ rake db:drop
(in /home/rob/project/)
$ rake db:create
(in /home/rob/project/)
$ rake db:migrate
(in /home/rob/project/)
$ rake db:populate


Besides just being horribly repetitive, this method requires you to wait to the completion of each migration before typing the next thus preventing you from grabbing the o so needed cup of coffee while your db is migrating. This method also reloads the rails environment before each migration which is completely useless and time consuming.

"Ok" you say, "I got the answer" and type the following:

$ rake db:drop && rake db:create && rake db:migrate && rake db:populate


This is one step better as you do not have to wait to the completion of each task before typing in the next, but we still have the problem of typing all of those commands as well as reloading the environment each time.

Sounds like its time for a custom rake task.
Lets create a rake file called db.rake and place it in our [project-root]/lib/task/ directory.

namespace :db do
task :everything => [:environment, :drop, :create, :migrate, :populate]
desc "Recreate everything from scratch including pre-populated data"
end
end


You can then just run one command

$ rake db:everything


For those of you familiar with Java you will noticed that the rake syntax of 'task :everything => [:environment, :drop, :create, :migrate, :populate]' is exactly like Ant's depends attribute.

For those of you not familiar with Ant ill quickly explain. Basically what 'task :everything => [:environment, :drop, :create, :migrate, :populate]' means is that the the task 'everything' depends on these other tasks so run those first.

Ok, so now we greatly improved our initial version in that we do not have to type all of the individual commands or wait for the completion of each task before continuing. We also get the benefit of not having to reload the environment before each task (other than the first time of course).

A problem arises though when we want to do anything else with this migration. For example, say we want to execute some logic or print out a simple message before and after each task execution. To accomplish this we would have to move all of the tasks out of the prerequisites section and move them into the actual task block. But how can we run another rake task from within a rake task? The Rake::Task api will help us find the answer we are looking for.

 task :everything => :environment do
desc "Recreate everything from scratch, including test data"
%w(drop create migrate populate).each do |task|
puts "Performing task #{task}"
#Some logic could go here
Rake::Task["db:#{task}"].invoke
end
end


The key to take out of this last example is the Rake::Task#invoke method. The method accepts a task (or tasks) and well... invokes it (or them).

So with the help of rake and Rake::Task#invoke we have a nice clean way of running multiple rake tasks.

1 comment:

Anonymous said...

Excellent post. I was looking for example this example of rake and db command automation and you had it!

Thank you :)