More User-centric Routes: Rails 2

Writing routes that are conditional upon whether a user is logged in is easy with Rails 3 but if you find yourself (as many of us do) stuck with a Rails 2 app, here’s how to achieve the same fancy routes without the latest Rails.

Required reading: User-centric Routing in Rails 3

What makes the approach above work for Rails 3 is that the routes have easy access to the request object. It’s not quite as easy with Rails 2, but with a little monkeying around we can still gain access to the request object from the routes.

The groundwork for this approach was laid by Jamis Buck and although his solution is a bit hackish, it works like a charm!

Before I go on to demonstrate how to use Jamis technique to check for a logged in user, I should mention that there’s a far better solution… Upgrade to Rails 3! If at all possible, that’s your best bet. Rails 3.0 is a huge improvement over Rails 2. Plus, Rails 3.1 is right around the corner! But if it’s just not a possibility, read on.

The Hard Part

In order to give the routes access to the request, we need to:

  1. Include login information that the routes can read
  2. Add a login condition that the routes can understand

First thing’s first. In config/initializers/logged\_in.rb, we can open up the route set and hijack the request environment to include whether or not a user is logged in.

class ActionController::Routing::RouteSet
  def extract_request_environment_with_logged_in(request)
    env = extract_request_environment_without_logged_in(request)
    env[:logged_in] = request.cookies.key?("user_token")

  alias_method_chain :extract_request_environment, :logged_in

Behind the long method names, this simply overrides an existing method and adds a key/value pair to the env hash that usually results. If cookies\["user\_token"\] is set, our user is logged in. And our routes can see that. But that’s only half the equation.

Next, in the same file, we open up the route and add a condition for our new environment information.

class ActionController::Routing::Route
  def recognition_conditions_with_logged_in
    result = recognition_conditions_without_logged_in
    result << "conditions[:logged_in] == env[:logged_in]" if conditions.key?(:logged_in)

  alias_method_chain :recognition_conditions, :logged_in

Once again, we’re simply overriding an existing method and tacking a condition onto the usual array of condition strings that would result. Condition strings? Yes, the old Rails routing internals were a little wonky.

The Easy Part

Now we’re ready to add the new condition to the application routes:

map.root :controller => "static", :action => "home", :conditions => {:logged_in => false}
map.root :controller => "users", :action => "show", :conditions => {:logged_in => true}

If you can put the slight ugliness in config/initializers/logged\_in.rb out of your mind, this works quite nicely and keeps your routes clean.

Strongly recommended reading: Upgrading to Rails 3