Hot Routes Feb 10 2007

Last day or so I’ve come up against a couple of intriguing Rails problems while trying to knock up some plugins for an app I’m writing. I’ll write up the second of the two problems later, but here goes the first…

Put simply, I wanted to define a route within my plugin. My plugin implemented a controller (and also contained a view for that controller, see one of the latest features to hit edge Rails ahead of Rails 2.0).

I searched to try and see if anyone had come up against a similar task, but didn’t see any concrete answers. My testing led me to realise that every time a call to ActionController::Routing::Routes.draw is made, it completely clears and re-generates routes based on the block passed to that method. So simply calling draw with a new block containing my required route wouldn’t work; it’d overwrite other routes, or end up getting overwritten by other routes later in the chain.

I then figured it was time to get cracking with some meta-programming, and that I was going to have to re-define and extend the draw method in order to be able to map the routes I want, and do it in a way that’d be flexible and fair to the original Rails routes file, and other plugins that wanted to toy around with routing. I came up with the following:


module ActionController
    module Routing
        class RouteSet
            alias draw_old draw
            def draw
                draw_old do |map|
                    map.connect "controller/action", :controller => "mycontroller", :action => "myaction"
                    yield map
                end
            end
        end
    end
end


Let’s give this a quick run-through… I’m re-opening the ActionController::Routing module, and specifically the RouteSet class where the draw method exists. I’m using alias to copy the existing draw method to a method name “draw_old”, before re-defining the draw method itself. Within my version of the draw method, I’m calling the old one and passing it a block that firstly defines the routes I wanted to add to the application, and that then yields the appropriate mapper object back to any block passed in to the draw method itself. Dependent on where you want your route to appear in the scheme of things, you could always map your plugins custom routes after you yield the mapper to the calling block, so that they appear after any other routes. Another tweak would be to alias the existing draw method to something like “draw_old_APPNAME”, where APPNAME is the name of your application. This would ensure that if multiple plugins implemented something similar, that they then wouldn’t clash.

There may be a better way to tackle the problem, but after half an hour of working the problem over (mad props to my boy Mike for helping me tweak the solution into something useful and re-usable), I think I came up with a pretty solid answer, and with a bit more time, something that could probably be wrapped up into a module or plugin all of its very own. Bottom line? Ruby as a language rocks, the dynamic nature and meta-programming features opens whole new doors of functional programming to me, and I’m having to really think outside of the box to see some of the amazing things you can do with it.

codingeleldiabloprogrammingrubytech