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:
- Include login information that the routes can read
- 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")
env
end
alias_method_chain :extract_request_environment, :logged_in
end
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)
result
end
alias_method_chain :recognition_conditions, :logged_in
end
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
Comments