Recently in Rake Category
A colleague of mind, Andy Kappen, just turned me onto some new hottness in Rake 0.8.0. Now, Andy doesn't have a blog, so I can't give him any link luv, but I can say that he and his crew held the #4 spot for Rock Band, so you know he's got some street cred. Unfortunately, a hardware failure and subsequent long turnaround time for repairs caused them to drop to the #22 spot.
Anyway, here are the Rake changes we'll focus on:
/lib/ruby/gems/1.8/gems/rake-0.7.3/lib/rake.rb
class Task
def invoke
end
end
/lib/ruby/gems/1.8/gems/rake-0.8.0/lib/rake.rb
class Task
def invoke(*args)
end
end
See the difference? In case you missed it, we can now call rake tasks with an arguments list (*args) instead of using environment variables.
Let's take a concrete example. Say you wanted to write a rake task to parse a given set of revisions from your subversion log. With Rake 0.7.3, the code might have looked like this:
namespace :subversion do
desc"Parse subversion log"
task :parse_log do
puts "parsing: svn log -r #{ENV['end_rev']}:#{ENV['start_rev']}"
end
end
And you could call this task with:
rake subversion:parse_log start_rev=200 end_rev=250
Now, there is nothing wrong with that. But, with the new hottness that 0.8.0 and *args provides, you can refactor your task to:
namespace :subversion do
desc"Parse subversion log"
task :parse_log, :start_rev, :end_rev do |t, args|
puts "parsing: svn log -r #{args.end_rev}:#{args.start_rev}"
end
end
And call it like this:
rake subversion:parse_log[200,250]
An added benefit of this new hottness is that our task descriptions will list the expected variables:
> rake -T
.
rake subversion:parse_log[start_rev,end_rev] # Parse subversion log
Simply sizzlin!
I was recently watching Geoffrey Grosenbach's third PeepCode episode focused on rSpec: rSpec Controllers and Tools.
At one point during the screencast, while working with Heckle, Geoffrey mentioned that it is easiest to run heckle against a single instance method of your own making due to the fact that all objects that are subclassed from ActiveRecord::Base have all the AR methods globbed in. No need to heckle Rails itself, eh? By the way, if you are unfamiliar with Heckle, Kevin Clark has a great overview.
The thing is, I want to heckle all my methods at once. A little A - B and a rake task is all it took.
namespace :spec do
desc "Heckle a class"
task :heckle => :environment do
unless ENV.include?("model")
raise "usage: rake test:heckle model=User"
end
#The user is allowed to test a single method
#with User#activate! or similar
inputs = ENV["model"].split("#")
model = inputs[0].capitalize
test_prefix = "#{model.downcase}"
klass = Object.const_get("#{model}")
#First check to see if user provided
#an instance method name. ex: User#activate!
methods = [inputs[1]] if inputs.size == 2
#If the user only passed in a model name,
#we need to get the instance methods that
#are not part of ActiveRecord::Base
methods ||= klass.instance_methods - ActiveRecord::Base.instance_methods
methods.each do |method|
cmd = "spec spec -H #{model}##{method}"
system(cmd)
end
end
end
If you would like to heckle all the methods for an object (we'll user a User object as an example), run the following:
> rake spec:heckle model=User
You can heckle a single method like so:
> rake spec:heckle model=User#encrypt
You can download this task as well as it's Test::Unit counterpart here: heckle.rake
The rake tasks have beend DRYed up a little so that we can run most of the same code regardless of whether you are using rpsec or Test::Unit.
Enjoy!
Sometimes when I'm building a Rails application, I want to rebuild my database from scratch. No problem you say? Just provide a lil' lighting? A lil' thunder? Or perhaps:
rake db:migrate VERSION=0
rake db:migrate
Well, yeah. That works if you only need to rebuild the database structure. But I often find myself writing plenty of rake tasks to bootstrap different parts of the system. For example:
namespace :bootstrap do
desc "Bootstrap a set of admin users"
task :admins => :environment do
puts "Bootstrapping admins"
end
task :categories => :environment do
puts "Bootstrapping categories"
end
task :zip_codes => :environment do
puts "Bootstrapping zip_codes"
end
end
Now, how would I go about running all these tasks at once? I could create an :all task that called each of bootstrap tasks:
namespace :bootstrap do
desc "Call all the bootstrap tasks"
task :all do
tasks = %w[admins categories zip_codes]
tasks.each do |task|
Rake::Task["bootstrap:#{task}"].invoke
end
end
end
This is fine if you only have a few tasks. But what if you add a new bootstrap task and fail to add it to the task list in :all?
My solution was to find a way to get all the tasks in a particular namespace. After reading through the Rake rdocs and fumbling around a bit, I finally resorted to bugging Mr. Rake himself, Jim Weirich, who provided the hint I needed:
Task manager objects are just objects that respond to the protocol required to hold and manage tasks. Currently, only the Rake::Application class responds to this protocol, but the task manager responsibilities were separated out into a module to make the list of required responsibilities more clear. You can get the current rake application object via: Rake.application
Ah! Rake.application. That's the ticket! So, here is what I ended up with:
namespace :bootstrap do
desc "Call all the bootstrap tasks"
task :all do
tasks = tasks_in_namespace("bootstrap")
tasks.each do |task|
Rake::Task["#{task.name}"].invoke
end
end
end
private
def tasks_in_namespace(ns)
#grab all tasks in the supplied namespace
tasks = Rake.application.tasks.select { |t| t.name =~ /^#{ns}:/ }
#make sure we don't include the :all task
tasks.reject! { |t| t.name =~ /:all/ }
end
You can get the sample file here: bootstrap.rake
A little rake luv is all you need:
> rake bootstrap:all
Bootstrapping admins
Bootstrapping categories
Bootstrapping zip_codes
Enjoy!