Modifying Rake Tasks

I paired with Yogi today on a few Rake tasks for our build. I mistakenly thought a Rake task could be overwritten by redefining it. That behavior may not be expected for build tasks, but it would be analogous to defining methods in Ruby:

class Person
  def first_name
    "Yogi"
  end
  
  def first_name
    "Dan"
  end
end
Person.new.first_name #=> "Dan"

For custom Rake tasks, you shouldn't need to modify them after the original definition. However, if you want to add behavior to some vendor tasks (such as those defined with Rails), this blog post will cover how to do that.

Defining a Rake Task Twice

When defining a Rake Task twice, the new prerequisites are appended to the existing ones, and the block for the task is added to the previously defined behavior. Here is an example:

task(:x) { puts "x" }
task(:y) { puts "y" }
task(:z) { puts "z" }

desc "First foo task"
task :foo => :x do
  puts "first foo task"
end

desc "Second foo task"
task :foo => [:y, :z] do
  puts "second foo task"
end

Running rake :foo outputs:

$ rake foo
x
y
z
first foo task
second foo task

As you can see, Rake executed the pre-requisites and actions for both task definitions. Rake also combines the task descriptions. Here is the output from rake --tasks (or rake -T):

$ rake -T
rake foo  # First foo task / Second foo task

Detecting if a Rake Task is Defined

Rake provides an easy mechanism for detecting if a task is already defined:

task :foo do
end

Rake::Task.task_defined?(:foo) #=> true
Rake::Task.task_defined?(:bar) #=> false

Adding Pre-Requisites to an Existing Rake Task

Although a developer can add pre-requisites to an existing task by using the task method, it's not a good idea. A developer could leave a comment that he or she was simply adding pre-requisites, but code should be able to document itself. Before providing a superior alternative, here is an example of what I'm saying is bad:

# assume foo is an existing 'vendor' rake task:
task :foo => [:add_prereq_1, :add_prereq_2]

Without the comment, the intention of adding pre-requisites to an existing task is not clear. However, using the enhance method makes the change much more intentional:

Rake::Task[:foo].enhance [:add_prereq_1, :add_prereq_2]

Adding Behavior to an Existing Rake Task

The enhance method can also add behavior that runs after the originally defined behavior.

Rake::Task["db:test:prepare"].enhance do
  Rake::Task["db:test:special_task_after_prepare"].invoke
end

Invoking the db:test:prepare task will now execute the original pre-requisites, the original action, and then the added db:test:special_task_after_prepare task.

Modifying a vendor Rake task is not a good idea unless you really need to. A new developer coming into the project will not expect that well-known tasks are doing something different (and you're likely to forget that you modified a task later). You may want to consider making changes like these in a file named something like extensions.rake. If you modify a task, make the changes evident and intentional by using Rake::Task#enhance.