Quantcast
Channel: Building Web Apps
Viewing all articles
Browse latest Browse all 27

Enhancing Conditional Routing in Rails

$
0
0

Rails’ routing infrastructure supports the concept of conditional routes: preconditions that must be satisfied before a particular route will trigger. Rails 2.1 supports one built-in condition, HTTP method checking, which is of some use but rather limited. What I needed was to be able to limit certain routes to only trigger when a particular host-name was used to access the application.

I thought I’d have to write messy additional logic until a little comment tucked away in ActionController::Routing::RouteSet and ActionController::Routing::Routing caught my eye. Here I briefly show you how to leverage this functionality for your own purposes.

The Goal — Conditional Routes in routes.rb

Let’s work backwards and see the result I was aiming for. I wanted to expand the existing capabilities of the routing engine and be able to restrict routes to specific hosts. The conditional routing option works by adding a parameter to your route specifications. Here are some examples:

map.with_options(:controller => 'feeds', :conditions => {:hosts => MY_HOSTS}) do |feed|
  feed.feeds_articles '/feeds/articles', :action => 'articles'
  feed.feeds_podcast '/feeds/podcast', :action => 'podcast'
end

or

map.resources :podcasts, :conditions => {:hosts => MY_HOSTS}, 
   :member => {:show_notes => :get, :transcript => :get},
   :collection => {:admin => :get} do |podcast|
     podcast.resources :comments, :member => {:report_as_ham => :get, :report_as_spam => :get}
   end

or even

map.connect ':controller/:action/:id', :conditions => {:hosts => MY_HOSTS}

In Rails 2.1, however, no such option :hosts exists, only an option to check the HTTP method via :method.

The Implementation

I haven’t really ever needed to use the conditional routing support before, and didn’t really think about it due to it only supporting the HTTP method check. For that reason, I originally thought I’d have to write my own logic, either patching existing Routing routines (nearly right!) or by writing new stuff that could get messy (bad idea).

During a last scan through the code for the keyword “conditions”, I saw this comment:

# Plugins may override this method to add other conditions, like checks on
# host, subdomain, and so forth. Note that changes here only affect route
# recognition, not generation.

Good, a place to start afterall! The solution is elegant as it only requires overriding two simple routines. You can do this in your own app by writing code that gets loaded at startup. Here is one implementation in its entirety:

require 'action_controller'

module ActionController
  module Routing
    class RouteSet
      def extract_request_environment(request)
        { :method => request.method, :host => request.host }
      end
    end

    class Route
      def recognition_conditions
        result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
        result << "conditions[:method] === env[:method]" if conditions[:method]
        result << "conditions[:hosts].include?(env[:host])" if conditions[:hosts]
        result
      end
    end
  end
end	

My code is very simplistic and tuned for my needs, but gives you an example of where to patch in. Here, I simply supply a list of host names I care about, and check the incoming host against that list.

Use extract_request_environment to parse out and store any data you will want to use in your conditional checks. This data will be available in the env hash later on.

recognition_conditions generates an Array of String objects that contain the Ruby code that will be used to build dynamic conditional test methods when the routing engine compiles the routes data in routes.rb.

I drop the source file into my project’s pre-existing lib/plugins/action_controller_extensions/lib directory as action_controller_extensions.rb and include an init.rb loader stub in my lib/plugins/action_controller_extensions directory:

require 'action_controller_extensions'	

My app deals with loading up such “plugins” at startup. You may have a different set-up. You can get the same effect by putting a require for the main source file in your startup code.

It would be great to see other generally useful conditionals contributed by the community.


Viewing all articles
Browse latest Browse all 27

Trending Articles