2099 Thunderhead Road
Suite 106
Knoxville, TN 37922
Phone Number (865) 238-5600
Monkey-patching Rails’ route generation

Monkey-patching Rails’ route generation

by Sean

On our way to a more RESTful Slamdot, we ran into a slight snag with the way Rails’ routes were being generated by Resources. We needed to be able to pass the user’s currently active domain name as a part of the URL, which turned out to be a little more difficult than we had hoped.

According to the Rails 1.2 RC1 release notes, the solution was simple:

Action Pack has an all new implementation of Routes that’s both faster and more secure, but it’s also a little stricter. Semicolons and periods are separators, so a /download/:file route which used to match /download/history.txt doesn’t work any more. Use :requirements => { :file => /.*/ } to match the period.

Only not so simple. At the time of this writing, resources don’t allow for a :requirements option to be passed on to the routes being generated. So, using Jamis Buck‘s incredibly-useful articles about routing, we were able to monkey-patch Routing to do exactly what we needed.

The code

module Slamdot
  module Routing
    module DSL
      module MapperExtensions
        def self.included(base)
          base.alias_method_chain :connect, :domain
          base.alias_method_chain :named_route, :domain

        def connect_with_domain(path, options = {})
          options[:requirements] = { :domain => /.*/ } if path.include?(':domain')
          connect_without_domain(path, options)

        def named_route_with_domain(name, path, options = {})
          options[:requirements] = { :domain => /.*/ } if path.include?(':domain')
          named_route_without_domain(name, path, options)

ActionController::Routing::RouteSet::Mapper.send :include, Slamdot::Routing::DSL::MapperExtensions

What it’s doing

We make use of the alias_method_chain idiom to wrap our custom connect_with_domain and named_route_with_domain methods around Rails’ default behavior. So now, when our routes are being generated, we intercept them and inject our :requirements option (if necessary). Then, we sneakily hand the route construction back over for completion.

That’s all for now!