« Bridging the Gap: Socially Responsible Investment Research and Consumer Ethics
Main
Largehearted Goat? »

learns_to Modules and Namespaces: Lessons from Wrapping the del.icio.us api

Last night, I started working on putting together a Ruby-wrapper for the del.icio.us api. I need it to execute this little idea I had recently (more about that when it's done) and I was surprised to find that there wasn't anything too useful out there -- though it's probably because the api is so easy to use you barely need a wrapper around it for most projects. There were a few libraries, but nothing really clean and complete and nothing using the new v1 of the api.

Anyway, in the course of working on the wrapper, I came across a common problem: the need for multiple namespaces. In the api, method names are not unique across objects. For example, there's a method that gets posts for a user and one that gets tags, both called "get" (api.del.icio.us/v1/posts/get and api.del.icio.us/v1/tags/get, respectively). Obviously, those urls leave no confusion as to which "get" method gets which type of object. The question is: what device in ruby should I use to capture this with equivalent clarity?

Two strategies occurred to me immediately: modules and subclassing. According to the relevant section of Programming Ruby, "modules are a way of grouping together methods, classes, and constants. . .[They] provide a namespace and prevent name clashes." Well, that sounds like exactly what I want to do. I want to group together the api methods for posts so that they don't pollute the namesapce for tags. Under this design, I would have multiple modules within my main class, one with the methods for each api "object," posts, tags, bundles, and whatnot.

So, to see if this would actually work, I ginned up a simple example of using modules inside a class. This is what it looked like: #namespaced class methods class Test module Gar def self.to_s puts "gar!" end end module Bax def self.to_s puts "bax!" end end end Test::Gar.to_s Test::Bax.to_s If you ruby this you'll see this output: gar! bax! In other words, it seems to work for class methods.

But what about instance methods. I made my toy example a little more complicated: class Best attr_accessor :dog def initialize @dog = "bot!" end module Gar def self.set_dog @dog = "gar!" end end module Bax def self.set_dog @dog = "bax!" end end end t = Best.new puts t.dog Best::Gar.set_dog puts t.dog Best::Bax.set_dog puts t.dog Unfortunately, this doesn't seem to work. The modules can't get access to the instance variable, @dog. The output ends up looking like this: bot! bot! bot!

This means that I'm thrown back to trying to solve the problem with regular subclassing. I'll be defining a series of classes like this: class Relicious attr_accessor :username, :password #my main class, connects to del.icio.us, etc. end class Post < Relicious def get #call the posts/get url end end clas Tag < Relicious def get #call the tags/get url end end That way, each separate subclass can implement identically-named methods with no danger of namespace confusion. My initial instinct was that this pattern was slightly less elegant than what I was trying to achieve with modules because the subclasses all have to access the centralized connection methods and such in the parent class. The resulting usage code looks like this: post = Post.new post.username = "myusername" post.password = "mypassword" post.get which is ugly (a post doesn't really have a username) and inefficient (you'd have to set the username and password attributes fresh if you called Tag.new since you'd have a new instance).

Thankfully, today Chris proposed a better solution, which, in retrospect, should have been obvious to me: wrapping up the child objects inside of accessors in the parent class and then only ever accessing them from there. This would turn the above usage code into this: rel = Relicious.new rel.username = "myusername" rel.password = "mypassword" posts = rel.posts.get The namespace problem is solved, everything is meaningfully encapsulated, and the syntax is concise and clear. Sounds like good design. Now, all that's left is to actually implement it. . .

Tagged: , , , , , ,

Posted by Greg at September 10, 2006 6:19 PM

Comments

A quick edit - your topic sentence reads like this:

"Last night, I started working on putting together a Ruby-wrapper for the del.iciou.us api."

I think this should read "del.icio.us" (the clever url tricks they invented get them in this sort of trouble all the time, I am sure...).

Posted by: Jem at September 11, 2006 4:29 PM

Thanks, Jem. I fixed it.

Posted by: Greg at September 11, 2006 7:58 PM

Post a comment




Remember Me?

(you may use HTML tags for style)