codahale.com٭blog

This is my old blog. My current writing is here: codahale.com

libxml-ruby is back!

Anyone who’s had to parse XML using Ruby has understood that REXML is more experiment than library. Alternatives have been few and far between, and most of us have settled for parsing documents with Hpricot and made do.

Well here comes Charlie Savage with some great news.

Not only is he promising to maintain libxml-ruby — the Ruby bindings for libxml2, one of the finest open source XML libraries — but he’s announcing v0.8.0, including some of the following tasties:

  • SAX
  • DOM
  • XPath
  • XPointer
  • XML Schema
  • DTDs
  • XSLT
  • no more segfaults

As someone who has spent a long time wishing that Ruby had first-class XML support…

THANKS CHARLIE.

Pints are on us.

Comments Off

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 »

Wesabe API vs. Quicksilver

As you may have noticed, we at Wesabe just released an API allowing you to access your financial data on your own terms.

So what do I do with APIs? I write little Quicksilver Applescripts for them. So here’s how you get a listing of your bank accounts with their current balances via Wesabe through Quicksilver.

The Script

(* Wesabe API Thingy for Quicksilver
 * (c) 2007 Coda Hale <coda.hale@gmail.com>
 * MIT License
 *)

--- pull out the username and password for a website
on getCredentials(serverName)
    tell application "Keychain Scripting"
        set ourKey to first Internet key of current keychain whose server is serverName
        set ourCredentials to (account of ourKey & ":" & password of ourKey)
        return ourCredentials
    end tell
end getCredentials

--- shell out to curl to download the accounts listing in XML
on downloadAccounts(credentials)
    set curlCommand to "/usr/bin/curl --silent --show-error --user " & quoted form of credentials & " https://www.wesabe.com/accounts.xml"
    set results to do shell script curlCommand
    return results
end downloadAccounts

--- use System Events to parse the XML and return a list of account names and balances
on getAccountBalances(xmlData)
    tell application "System Events"
        set accountBalances to ""
        set xmlDocument to make new XML data with data xmlData
        set accountElements to XML elements of XML element "accounts" of xmlDocument whose name is "account"
        repeat with accountElement in accountElements
            set accountName to value of (XML element "name" of accountElement)

            if (XML elements of accountElement whose name is "current-balance") ≠ {} then
                set accountBalance to value of XML element "current-balance" of accountElement
            else
                set accountBalance to "(none)"
            end if

            set accountBalances to accountBalances & accountName & ":  " & accountBalance & "
"
        end repeat
        return accountBalances
    end tell
end getAccountBalances

--- actually execute this stuff
set xmlData to downloadAccounts(getCredentials("www.wesabe.com"))
set balanceText to "Your Wesabe Accounts:
" & getAccountBalances(xmlData)

--- and put it on the big screen
tell application "Quicksilver"
    activate
    show large type balanceText
end tell

Installation

Toss the above into Script Editor, and save it as an Application someplace where Quicksilver will pick it up (I called mine “Wesabe Accounts”). You should have an entry in your Keychain for wwww.wesabe.com — that’s where the script reads your username/password.

Using The Damn Thing

Run it from Quicksilver. It’ll look like this:

Wesabe via Quicksilver

If you want it to do something different, go for it. Add some functionality, put it up on your blog, and add a comment here with a link there.

I’m really interested to see what kind of stuff people make with this.

5 comments »