Monkey-patching Rails' route generation

    Jan 13, 2007

    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
            end
    
            def connect_with_domain(path, options = {})
              options[:requirements] = { :domain => /.*/ } if path.include?(':domain')
              connect_without_domain(path, options)
            end
    
            def named_route_with_domain(name, path, options = {})
              options[:requirements] = { :domain => /.*/ } if path.include?(':domain')
              named_route_without_domain(name, path, options)
            end
          end
        end
      end
    end
    
    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!

    More On This Topic