Constrain Yourself

Josh Clayton

Most Rails developers have written apps where you have a marketing homepage and a route that you should be redirected to after signing in. There may even be different pages to be redirected to after signing based on some sort of role system.

Who wants to remember that route across the app? No one.

Instead of writing a nasty helper to determine where to send a user after signing in, let’s have Rails handle this for us.

Enter Rails routing constraints.

It turns out Rack is a beautiful piece of software! With Rails routing constraints, we can re-use the Rails’ root to handle all this logic for us. This means no more remembering where root_url will take you or stuffing all that logic into some giant switch statement!

For example, I recently wrote an app using Clearance and high_voltage. When visiting the root path without being signed in, I wanted to render high voltage’s ‘homepage’ view. If the user was signed in, I wanted to display an administrative page where they could manage their assets.

My routes file looks like this:

root to: "assets#index", constraints: Clearance::SignedInConstraint
root to: "high_voltage/pages#show", id: "homepage"

Two roots! My constraint to hitting assets#index is based off my class Clearance::SignedInConstraint, which I’ve thrown in lib/.

# lib/clearance/signed_in_constraint.rb
module Clearance
  class SignedInConstraint
    def self.matches?(request)
      request.env[:clearance].signed_in?
    end
  end
end

This looks to request.env[:clearance] (introduced in Clearance 0.13), which returns a Clearance::Session instance, and allows us to call #signed_in?.

With Rails constraints, if .matches? returns true, the constraint is fulfilled and the router allows assets#index to do its thing. When false, the route ‘falls through’ and the next root kicks in.

Constraints are a great way to scope routes based on particular preconditions and may end up cleaning up a handful of nasty conditional logic in a view helper. Constraints can be great for things like subdomains as well, but I’d love to hear about some more inventive uses.