Learning from Rails design

Since version 2.0, “Wii Transfer”:http://www.riverfold.com/software/wiitransfer/ has had a built-in web server for serving music and photos to the Nintendo Wii. The server was written in Cocoa and the code became very unwieldy as I continued to add features. Dozens of methods for processing different parts of the URL, and many if statements for conditionally branching based on the URL, splitting the URL parameters, and more.

The code looked something like this (only worse):



if (path startsWithSubstring:@"/albums") {

[self processAlbumsRequest:path];

}

else if (path startsWithSubstring:@"/artists") {

[self processArtistsRequest:path];

}

else {

// ...

}

(void) processAlbumsRequest:(NSString *)inPath

{

// split out the parameters and request extensions from the URL path

// ...

if ([e isEqualToString:"xml"]) {

// ...

}

else if ([e isEqualToString:"html"]) {

// ...

}

}

Multiply that times 10 for all the different URLs that Wii Transfer knows how to process, and you can see how it worked fine for a couple simple things, but quickly became a mess as I added features.

For the upcoming version 2.3, I redesigned most of the URLs to follow a common structure, patterned after the default URL syntax that Rails uses: /controller/action/id. Now, instead of if statements, I dynamically route the URL requests using NSSelectorFromString() and performSelector:withObject:.

Consider this code (as above, simplified from the real thing):



// extract the values from /controller/action/id URLs

NSArray* pieces = [[path stringByDeletingPathExtension] pathComponents];

NSString* controller = [pieces objectAtIndex:1];

NSString* action = [pieces objectAtIndex:2];

NSString* param_id = [pieces objectAtIndex:3];

// call a method named controller_action, passing it the id

NSString* sel_name = [NSString stringWithFormat:@"%@_%@:", controller, action];

SEL method = NSSelectorFromString (sel_name);

[self performSelector:method withObject:param_id];

Now if I need to invent a new URL, say “/tracks/play/1234.mp3”, all I have to do is write the implementation for that method:



- (void) tracks_play:(NSString *)inParamID

{

// ...

}

The web request calls through to this new method without any additional glue code, in this case passing “1234” in the single parameter.

(The underscored method signatures aren’t very Cocoa-ish, but this is actually a plus because I can quickly spot the chunk of the controller that processes a set of requests, and I like that they read just like the URLs. I’m also currently using a single controller instead of having separate controller objects for the different types of requests, but I may expand that later.)

This convention has also allowed me to simplify all the URLs that Wii Transfer uses. Other examples include “/covers/search/U2” or “/artists/show/5”. I’ve eliminated a bunch of code, and it fits nicely with how I serve application resources and the start of a HTML template system.

Could it be taken further? Sure. I remember in the Mac OS 9 days building a web interface for a product using only compiled AppleScript scripts stored in the resource fork. Lately, folks like “Gus Mueller”:http://gusmueller.com/blog/ and Adobe’s Lightroom team have been doing interesting things with “embedding Lua”:http://www.sqlabs.net/blog/2006/01/adobe-lightroom-and-lua.html. I don’t want that level of extensibility yet, but it seems like a logical next generation when I outgrow even this new web architecture in Wii Transfer.