adventures in making stuff with Daniel Higginbotham

Aikidoka Prevents Namespace Collisions

10 May 2009

Recently I fell victim to the Twitter-Mash / Extlib-DataMapper-Mash namespace collision. To get around this problem, I've created a new gem, Aikidoka.

Here's what happened when I tried to use Twitter when Extlib had already been loaded:

require 'Twitter'
# => ["Twitter"]
Twitter::Search.new("bokken").fetch
# SystemStackError: stack level too deep

Here's what happens when you use Aikidoka:

require 'aikidoka'
# => ["Aikidoka"]
Aikidoka.rename("Mash" => "Twitter::Mash"){require 'twitter'}
# => ["Mash"]
Twitter::Search.new("aikidoka").fetch
# <Mash completed_in=0.052875 max_id=1754360060 next_page="?page=2&max_id=1754360060&q=aikidoka">

It works! What this does is namespace the Mash defined when I require the Twitter gem, so that Mash is now Twitter::Mash. Also, Extlib's Mash is still there, untouched, so you don't need to worry about that. Here's how Aikidoka does its magic:

  • It temporarily renames existing constants so that they don't get clobbered. In this case, "Mash" is renamed to "AikidokaMash". Right now this only works with top-level constants.
  • It yields to the given block. This block should define the constants you want permanently renamed/namespaced. In this case, we're requiring "twitter", which in turn requires "mash". "mash" defines the constant we want to rename, Mash.
  • It creates modules as necessary to create the namespace. In this case, the module Twitter is already defined so that's used. However, if we wanted to rename "Mash" to "Potatoes::Mash", then a module named "Potatoes" would have been created.
  • It assigns the object referred to by the old constant to its new constant. "Twitter::Mash" now refers to the same object that "Mash" refers to.
  • Old constants are removed to clean up the namespace. The constant "Mash" no longer exists, the object it used to refer to lives on.
  • The constants temporarily renamed in step 1 are now given their original names back. Extlib's "Mash" is no longer "AikidokaMash"; it's "Mash" again.

The code is very simple - a total of 67 lines in one file with decent specs - so hopefully it's easy to dig into.

Right now Aikidoka is best at nesting an existing top-level constant within another constant of a different name. I haven't tried doing something like Aikidoka.rename("Mash" => "Mash::Twitter") or Aikidoka.rename("ActiveRecord::Base" => "ARBase"), and those examples probably wouldn't work.

All in all, it does what I want it to and seems to work OK :) You can install it with gem install flyingmachine-aikidoka. If you're wondering about the name, aikido is a martial art designed to resolve conflict harmoniously, and an aikidoka is a student of aikido.

Comments