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


Sam Ruby: Routing (Around|With) Rails

[link]...

Excerpt from del.icio.us/tag/rails at

I read this article about an hour ago, and mulled it over a bit, and came up with a couple of thoughts for you, Sam.  I’m not a real expert at Rails yet, but if I were to tackle the MultiViews aspect of the problem, the first thing I’d look at is whether filters would do the trick. This would allow you to write the rendering code once.

The other thought I had was to have a look at the render routine.  One feature that you can leverage is specifying which layout you want to use.  What I’d do here is pass the flav in there as a layout name, and construct templates in whatever format is required by that flav.

It would be the latter option I’d pick, combined with your first attempt to solve the MultiViews challenge.  The beautiful thing about all this is that there are so many ways to solve the problem.

Posted by Robert Hahn at

Thanks, Robert.

I took a look into this.  before_filter basically does what I need.  I have to access params as controller.params but other than that, things just work.

Unfortunately, after_filter is invokes too late to do what I want.  What I would ideally want after_filter to do is

render :action => params[:flavour]

Unfortunately, if no other rendering is done by the time the action is complete, Rails will attempt to render a default template by the same name as the action — before invoking after_filter.

Posted by Sam Ruby at

Hey, Sam

I was kind of afraid of that (re: the after_filter business). I had another thought since my last post. The render() method is a method inherited from the ApplicationController, and I bet it hasn’t been frozen.  That means that you can override the defaults simply by writing your own render() method that tests for, then leverages @params[ :flavour ] if it exists.

Posted by Robert Hahn at

It looks like there is an even easier solution: override the default_template_name method in the controller in question.

Posted by Sam Ruby at

Hi Sam,

It sounds like the ideas you have for dispatching in Rails based on the HTTP request method are similar to mine.  We’ve been discussing RESTifying Rails on the uf-rest mailing list the last week or so and this subject has come up.  DHH came up with one approach, and I created a prototype controller that implements it — thought you might be interested: [link]

Posted by Dan Kubb at

Sam, did putting the default_template_name method in the controller work for you?
my override never calls.

Posted by jason at

Jason: yes.  Here’s the code I used (look to the bottom).  I haven’t checked to see if it still works with Rails 1.0.

Posted by Sam Ruby at

Hi Sam, I found where my problem is , but  I can’t think of a way to solve it. The problem is I need to do some data processing before I know what view should be rendered.
so I have something like:

def default_template_name
  “@result_from_controller”
end

I am not sure if this is possible.  after_filter is too late, but default_template_name is called too early , before the action code is called.
I couldn’t find your email, what is your email?

Posted by jason at

If you want to send me email, send it to anything at this website, but in general, I respond better to comments on my weblog.  If you prefer email, I’d suggestion rails@lists.rubyonrails.org

With the version of Rails that I used in September, default_template_name was called at the time any render-type method was called.  If no render method was called explicitly, one is called on your behalf after the action code completes.

Posted by Sam Ruby at

Add your comment