By default Rake Tasks append behavior every time they are defined. The following example shows that both definitions are executed.
task :the_task do
p "one"
end
task :the_task do
p "two"
end
Rake::Task[:the_task].invoke
# >> "one"
# >> "two"
I like this behavior, but sometimes you want to overwrite a task instead of appending to what's already there.
When you no longer want the existing behavior the
overwrite
method can come in handy.
@actions.clear
enhance(&block)
end
end
task :the_task do
p "one"
end
Rake::Task[:the_task].overwrite do
p "two"
end
Rake::Task[:the_task].invoke
# >> "two"
The overwrite method is good, but sometimes you want to redefine the task using one of the specialized Rake tasks. I recently wanted to redefine the test task, so I created the
abandon
method to remove the existing definition.
@actions.clear
end
end
task :the_task do
p "one"
end
Rake::Task[:the_task].abandon
Rake::TestTask.new(:the_task) do |t|
t.libs << "test"
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
Rake::Task[:the_task].invoke
# >> Expectations ..................
# >> Finished in 0.00442 seconds
# >>
# >> Success: 18 fulfilled
Hopefully you wont need this type of thing very often, but it can be handy when you want to overwrite a task that has been previously by a framework you've included.
Update below...
Ola Bini pointed out that I'm not clearing the prerequisites. Clearing the prerequisites is something you can do without any modifications to rake.
Rake::Task[:task_name].prerequisites.clear
Also, if you want prerequisites cleared as part of overwrite or abandon, you can easily add it to both tasks.
@actions.clear
prerequisites.clear
enhance(&block)
end
prerequisites.clear
@actions.clear
end
end
I've been thinking about adding an explicit API in rake to do something similar to your overwrite and abandon examples. The thing that makes me pause is that is doesn't play nice with plugins. Its one thing to clear a task as a project owner, since you are assuming responsibility for the entire project. However, as writer of a Rakefile module (such as the .rake files in a rails project), using abandon or overwrite clearly has the potential of overwriting someone else's overwrites. I've not come up with a good solution for that.
ReplyDeleteHello Jim, thanks for the comment.
ReplyDeleteI think your concern is a valid one, but no different than the same threat that Open Classes pose. With great power...
I also think you might as well add overwrite and abandon to Rake since the cat's already out of the bag. RSpec wrote their own redefine_task method and after I finished with my blog post, I found someone else who had written their own overwrite method. It's basically public knowledge, so adding it to the framework is the next logic step, IMHO.
Cheers, Jay
Awesome post, to this day (more then 2 years of rails dev), I never understood why redefining a rake task didn't _redefine the rake task_. I guess I can see the benefit of appending the behavior. But It's not the first thing I would have thought, thanks!
ReplyDeleteI've seen a couple people suggest, instead of your abandon(), to basically do: Rake.application.instance_variable_get('@tasks').delete(task_name)
ReplyDeleteE.g.
http://matthewbass.com/2007/03/07/overriding-existing-rake-tasks/
http://rubyizednrailified.blogspot.com/2008/07/remove-rake-tasks.html
Do you have thoughts on this versus your approach of clearing the actions from the task?
Also, in my case I was trying to override rdoctask stuff, which turns out to be a bit trickier. In order to override the standard Rails rdoctask, declared as Rake::RDocTask.new('app') do..., I had to remove the tasks 'doc:app', 'doc:reapp', 'doc:clobber_app', AND 'doc/app/index.html'. There would probably be a nice way to write this all into a call to a new method Rake::RDocTask.remove()...
Peter,
ReplyDeleteI'd definitely try to get Jim's opinion since he's Rake's creator. His opinion will be the most important one.
I'm guessing the 'abandon' method is probably better than screwing around with instance variables, but I honestly have no idea. Maybe the instance variable route is the way to go. This blog post is a bit old, so maybe it's dated.
Cheers, Jay
Also need to clear @full_comment if you want to abandon the original description.
ReplyDeleteclass Rake::Task
def abandon
@full_comment = nil
clear_actions
clear_prerequisites
end
end
Example:
namespace :db do
namespace :schema do |schema|
schema[:dump].abandon
desc "Nothing because it's too slow in Oracle"
task :dump => :environment do
# nothing
end
end
end
Is there any kind of precedence in the "Make" world? Make has been around longer than Rake.
ReplyDeleteYou wrote "I like this behavior, but sometimes you want to overwrite a task instead of appending to what's already there."
I don't quite see how the first behaviour is useful. What examples do you have for when it is useful to overwrite a task? To me that would just be a mess.
Stephan
Now this is part of rake through the clear method:
ReplyDeletetask :test do
puts "first implementation"
end
task(:test).clear
task :test do
puts "second implementation"
end