How can I redirect a user's home (root) path based on their role using Devise?
Asked Answered
C

5

32

I'm working on a project management app, and in the app, I have project_managers and clients. I'm using Devise and CanCan for authentication/authorization.

At what point after login should I be redirecting the user to their own specific controller/layout/views? Is there a way to check for current_user.role in routes.rb and set the root (or redirect) based on whether or not they're a project manager or a client? Is this a change I can make in Devise somewhere?

Thanks in advance for any help! --Mark

Cordwood answered 20/1, 2011 at 23:28 Comment(1)
I asked a similar question: #3799893 - the answer there was to create a separate controller for the homepage that does the routing. You can't access that Devise information inside routes.rb.Cornerwise
K
41

Your routes.rb file won't have any idea what role the user has, so you won't be able to use it to assign specific root routes.

What you can do is set up a controller (for example, passthrough_controller.rb) which in turn can read the role and redirect. Something like this:

# passthrough_controller.rb
class PassthroughController < ApplicationController
  def index
    path = case current_user.role
      when 'project_manager'
        some_path
      when 'client'
        some_other_path
      else
        # If you want to raise an exception or have a default root for users without roles
    end

    redirect_to path     
  end
end

# routes.rb
root :to => 'passthrough#index'

This way, all users will have one point of entry, which in turn redirects them to the appropriate controller/action depending on their role.

Kaela answered 20/1, 2011 at 23:36 Comment(4)
#4754371 is a better implementation, as it does not create a new controller just for redirection. Also, this would result in 1 extra round trip from browser to server every time a user is redirected to root_pathExon
Elegant solution that does not require to change any existing code, i.e. I can keep all redirect_to root_path statements as they were before. I like it :)Rune
This works nicely for me. I decided to throw it in my application controller, and everything seems dandy... But is there any reason you set up a brand new controller for it? Just wondering if I'm overlooking something.Generally
This controller can easy be bypass if the user enters directly another path in the url. :(Licit
O
23

Another option is to pass a proc to the authenticated method like this (I'm using rolify in this example):

authenticated :user, ->(u) { u.has_role?(:manager) } do
  root to: "managers#index", as: :manager_root
end

authenticated :user, ->(u) { u.has_role?(:employee) } do
  root to: "employees#index", as: :employee_root
end

root to: "landing_page#index"

Note that in Rails 4 you have to specify a unique name for each root route, see this issue for details.

Occidentalize answered 2/2, 2014 at 17:14 Comment(2)
let's vote up for this answer! should be accepted as correctCoenzyme
Is there a way to use the user in deciding the path? I.e. root to: organization_path(user.organization)Ribaudo
T
22

The simplest solution is to use lambda:

root :to => 'project_managers#index', :constraints => lambda { |request| request.env['warden'].user.role == 'project_manager' }
root :to => 'clients#index'
Trant answered 3/8, 2012 at 11:14 Comment(2)
It worked for me. I have a question though. I'm using Ruby 2.3.0 and it allows me to use new stabby lambda syntax. But I'm getting syntax error with constraints: -> { ... }. It works fine if I use lambda instead of ->. Any idea about this?Hokanson
It's supposed to be possible to use a proc/lambda for the :to option (root to: proc { |env| ...}), but I ran into various problems with that since it seems to break url_for. This solution worked without breaking url_for. +1!Belligerence
L
8

I do this in a Rails 3 app that uses Warden. Since Devise is built on top of Warden, I think it'll work for you, but be sure to experiment with it a bit before relying on it.

class ProjectManagerChecker
  def self.matches?(request)
    request.env['warden'].user.role == 'project_manager'
  end
end

# routes.rb
get  '/' => 'project_managers#index', :constraints => ProjectManagerChecker
get  '/' => 'clients#index'

If the user's role is "project_manager" the ProjectManagersController will be used - if it's not the ClientsController will be used. If they're not logged in at all, env['warden'].user will be nil and you'll get an error, so you'll probably want to work around that, but this will get you started.

Lowther answered 21/1, 2011 at 0:2 Comment(1)
Thanks for answering! I actually tried this one first, but I couldn't get it to work (probably because I'm new to this). Not to mention- I can barely figure out what I'm doing at this point, so the idea of "experimenting with it" is scary as hell!Cordwood
E
3

Blog post with the implementation http://minhajuddin.com/2011/10/24/how-to-change-the-rails-root-url-based-on-the-user-or-role/

Exon answered 24/10, 2011 at 9:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.