intertwingly

It’s just data

Routing (Around|With) Rails


I have two use cases for routing that I would like to explore.  First is a common MultiViews pattern by which the file extension is used to determine either the action or the template.  The second is the common REST paradigm by which all resources of a given type are handled by the same controller, but the action isn’t determined by the URI, but instead by the HTTP request method used.

To make this more concrete, consider an HTTP GET of the following URI:

http://example.com/blog/10.html

The goal is to parse that URI into

{'controller'=>'blog', 'action'='get', id='10', 'flav'=>'html'}

... where flav is short for flavour, the name that blosxom gives to such things.

I experimented with a number of things to make this work.  For the MultiViews case, one possible solution is to simply have the routing inhale the last component of the path as :id or even *path, and have each action pick apart the parameter.  But that wouldn’t exactly be DRY would it?  Nor would sprinkling each action with request.get? queries.

My second solution involved a double dispatch, i.e., having multiple maps, each of which resolves to the same action, where that action does some common setup, and then re-dispatches (perhaps by send params[:subaction], or even send request.method).  The result was fairly satisfactory.

Still exploring, my third solution involves introducing mod_rewrite functionality into ActionController::Routing::RouteSet.  It might not be the most practical solution long run, but building it certainly was educational.

Simply by doing a require 'mod_rewrite', a rewrite method is made available to map, and the recognize! method in the ActionController::Routing::RouteSet class is dynamically replaced.  Furthermore, each time the recognize! method is called, the request in question is modified so that two instance methods inherited from the base class are replaced by attribute accessors.  The list of rewrite actions are simply an array of blocks which are executed in sequence.  In the simplest case of a pattern and a replacement the block is actually a full blown continuation.

All in all, the hardest thing about this solution was ensuring that things are reset properly between calls.  CGI is truly only expected to be called once per process, opening the possibility of unit tests ‘bleed through’.  Also, apparently rails will call draw multiple times, which would mean that the rewrites would get duplicated.  Neither were difficult to address, and neither would be a problem if this functionality were properly folded into rails itself.

In an IM conversation with David Heinemeier Hansson earlier today, I got the impression that he prefers tailored solutions that solve specific needs well as opposed to  over-engineered solutions that solve general needs poorly, and I can respect that.  However, the fact that I could independently build and integrate such a general purpose addition is a testament to the power of Ruby.

source, unittest