I love RSpec, and lately I’ve been making the transition from test-friendly development to full-on spec-driven development. I still toss around some code for proofs of concept or to prototype APIs, but when the time comes to write serious code, I always begin with a spec.
I was working on a project recently which boiled down to “run these tasks in this order,” which is a natural fit for Rake. I have lots of beef with Rake, but I was able to back away from the yak-shaving precipice with this:
def describe_rake_task(task_name, filename, &block)
require "rake"
describe "Rake task #{task_name}" do
attr_reader :task
before(:all) do
@rake = Rake::Application.new
Rake.application = @rake
load filename
@task = Rake::Task[task_name]
end
after(:all) do
Rake.application = nil
end
def invoke!
for action in task.instance_eval { @actions }
instance_eval(&action)
end
end
instance_eval(&block)
end
end
Drop that in your spec_helper.rb and you can do stuff like this:
describe_rake_task "build:my_thing", "lib/tasks/my_thing.rake" do
it "should build my other thing first" do
task.prerequisites.should include("build:my_other_thing")
end
it "should do something" do
@built_my_thing = false
Builder.should_receive(:build).with(:my_thing)
invoke!
@built_my_thing.should be(false)
end
end
Which, when run, produces this:
Rake task build:my_thing
- should build my other thing first
- should do something
The invoke! method runs the task’s action(s) inside the spec’s instance, which means you can mock out methods and handle changes to instance variables.
Doesn’t much change my opinion of Rake, but at least I’ve got a better way to write tasks for it.
4 comments »
Update: This behavior was removed from ruby2ruby 1.1.8 — upgrade and make sure your code works.
From #sequel
[4:03pm] apeiros: ciconia, does sequel add a method_missing that swallows all to NilClass?
[4:05pm] codahale: I think it does.
[4:05pm] codahale: Yeah, it does.
[4:07pm] codahale: I’d seen that behavior in some of my specs — wasn’t sure where it was coming from.
[4:15pm] codahale: Actually, that’s in ruby2ruby.
[4:16pm] codahale: Yeah, line #8 in ruby2ruby.rb.
[4:18pm] codahale: apeiros: Is the NilClass#method_missing a real problem for you, or just an unexpected quirk?
[4:19pm] apeiros: it’s a real problem
[4:19pm] apeiros: it’s a bugkiller
[4:19pm] apeiros: or better said: a debugging/bugfinding killer
[4:20pm] apeiros: an unexpected nil is carried on far far longer than it should and normally would. makes debugging PITA.
Lines 8-12 of ruby2ruby.rb
class NilClass # Objective-C trick
def method_missing(msg, *args, &block)
nil
end
end
An experiment:
>> nil.why_god_why
NoMethodError: undefined method `why_god_why' for nil:NilClass
from (irb):1
>> require 'ruby2ruby'
=> true
>> nil.why_god_why
=> nil
>> @no_se_existe.please_to_be_raising_error_now
=> nil
Just so’s you know.
3 comments »
Holy crap. I’m writing plugins.
I’m working on a data warehouse, and I was building the migration for a dimension table today. Dimension tables are these incredibly wide (50-100 columns), denormalized tables which have to be heavily indexed in order to work well. And my migration was totally out of hand. I wanted to index each column, but that lead to a stupid amount of repetition.
I had something like this1:
create_table :date_dimensions do |t|
t.datetime :date
t.integer :day_of_month
t.integer :day_of_year
t.integer :week_of_month
# etc.
end
add_index :date_dimensions, :date
add_index :date_dimensions, :day_of_month
add_index :date_dimensions, :day_of_year
add_index :date_dimensions, :week_of_month
# etc.
I had typed :date_dimensions five times, add_index four times, and each column name twice. And I still had like forty columns to do. So I wrote a quick little plugin to compress all that into this:
create_table :date_dimensions do |t|
t.datetime :date, :index => true
t.integer :day_of_month, :index => true
t.integer :day_of_year, :index => true
t.integer :week_of_month, :index => true
# etc.
end
Get it here: http://code.google.com/p/easier-indexes/
1 This is using the new “sexy” migration style from Edge Rails. Like most sexy things, Chris Wanstrath came up with it.
6 comments »
So Google Video has a bunch of really awesome content, but watching it with Flash is a CPU-hogging, lowest-common-denominator experience. So here’s a quick script to go from video URL to watchable AVI:
#!/usr/bin/env ruby
require "open-uri"
puts "Downloading descriptor file..."
gvp_id = ARGV[0].gsub(/\D/, “”)
gvp_doc = open(”http://video.google.com/videogvp/gvp-download.gvp?docid=#{gvp_id}”).read.split(”\\n”)
gvp_doc.find { |x| x =~ /^url:(.*)$/ }
gvp_movie_url = $1.gsub(/&/, ‘\\\\\\&’).gsub(/\\?/, ‘\\\\\\?’)
gvp_doc.find { |x| x =~ /^title:(.*)$/ }
gvp_title = $1
puts “Downloading \”#{gvp_title}\”…”
puts “wget -O #{(gvp_title + “.avi”).inspect} #{gvp_movie_url}”
If you want to use curl instead of wget, change the last line of code.
This probably violates some terms of use, so this is only a intellectual demonstration of the wonderment and possibilitude of the programmablemated webernet.
BTW, it’s amazing how much I had to escape things to pass the right values through Wordpress, Ruby’s weird-ass string escaping, and finally Bash. Lots of backslashes.
9 comments »
Just a general ping: I’m headed to RailsConf on Wednesday.
I look like this:

If I’ve had coffee, my eyes’ll be a bit wider, and I’ll be yelling things about rails-core and patches. If I’m sleepy, uh, I’ll squint and grumble. Here’s my day-time ticket.
So yeah, yell at me, or buy me a drink or something.
2 comments »