codahale.com٭blog

southern linc ringtonesolympic theme ringtonedem franchise boyz ringtonebox car racer ringtoneaudiovox cdm8900 ringtone
Coda Hale lives in Berkeley, CA, where he writes about Ruby on Rails, usability, web design and development, and the occasional bit about bicycles.

Rake vs. RSpec! Fight!

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 »

NilClass, Devourer Of Errors

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 »

Rails Plugin: easier-indexes

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 »

GVP Download: A quick and dirty Ruby script to download Google Videos

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 »

Headed to RailsConf

Just a general ping: I’m headed to RailsConf on Wednesday.

I look like this:

Me

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 »