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.