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

| | Comments (2)

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: , , , , , ,

Categories

2 Comments

Jem said:

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...).

Greg said:

Thanks, Jem. I fixed it.

Leave a comment

About this Entry

This page contains a single entry by published on September 10, 2006 6:19 PM.

Bridging the Gap: Socially Responsible Investment Research and Consumer Ethics was the previous entry in this blog.

Largehearted Goat? is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Powered by Movable Type 4.0
Clicky Web Analytics