A path through Enumerable

In Cocoa, you can poke inside object graphs (and more!) using dotted strings:

 NSDictionary *bestOfBeatles = @{@"paul": @{@"beatles": @"Hey Jude", @"solo": @"Jet"}};

NSString *solo = [bestOfBeatles valueForKeyPath:@"paul.solo"];
NSLog(@"Paul's Best solo song is: %@", solo);

Hey, did you notice that Objective-C sprouted some really handy literals for arrays and dictionaries lately? One could reasonably extrapolate that Apple has an interest in not breaking people’s fingers when they build for iOS and OS X.

There are libraries, e.g. Hashie, that make uniform access into object graphs, like you might get from an API response, as close to idiomatic Ruby as you probably want. Key paths, as implemented in Cocoa’s Key-Value Coding APIs, takes a different approach, using strings to define paths to traverse an object graph. I don’t really like “string programming”, but it’s an interesting approach, so let’s see where it takes us. Let’s add something like “key paths” to Ruby!

Hark, an implementation

Enumerable is an awesome thing about Ruby. Let’s build key path traversal on top of that. The goal is something close to the Objective-C snippet above:

 best_beatles = KVC.new({"paul" => {"beatles" => "Hey, Jude", "solo" => "Jet"}})
solo = best_beatles.for_path("paul.solo")
puts "Paul's best solo song is: #{solo}"

Turns out this isn’t hard to implement, even with a little error handling thrown in:

require 'delegate'

class KVC < SimpleDelegator

  def for_path(path)
    segments = path.split('.')

    segments.inject(self) do |value, s|
      if !value.respond_to?(:has_key?)
        raise ArgumentError.new("Expected #{value} to respond to has_key?")
      end

      if value.has_key?(s)
        value[s]
      else
        raise KeyError.new("Missing key #{s}")
      end
    end
  end

end

OK, first off, did you realize that SimpleDelegator makes it ridiculously easy to wrap objects with extra behavior? It sure does! Second, you could easily write this with a while loop or even a simple each, but why have local variables hanging around when you could use inject instead?![1] Third, this has more conditionals than I’d like, but it fits within 80 columns[2] so I’ll take it.

The pros and cons of using key paths

I’ve got this code, based on a whim. I’m not even sure it’s a good idea. Maybe it is!

  • It’s simpler to specify a traversal into a deep object graph with a string than with a bunch of key lookups or method chains. It’s slightly clearer too.
  • “Selector APIs”, like XPath or jQuery’s extensions to CSS, are an idea many developers have already wrapped their head around. I wouldn’t want to go anywhere near the complexity of those APIs, but an opinionated set of operations on an object graph might not get too complex.

Maybe it sucks?

  • Enumerable is pretty great, why wouldn’t you use that?
  • Programming by banging strings together is terrible.

Judgement

I can’t really tell you, definitively, if you should try this or not. I’ve barely even used KVC in Cocoa! It’s a nifty idea, though, and it’s pretty rad that you can implement something like it in Ruby so trivially.

Now you know!


  1. I realize this is a point of contention in the Seattle.rb ranks  ↩
  2. Forgive me now, Seattle.rb?  ↩
Adam Keys @therealadam