如何使用Devise根据用户角色重定向用户主路径?

30
我正在开发一个项目管理应用程序,在该应用程序中,我有“项目经理”和“客户”。我正在使用Devise和CanCan进行身份验证/授权。
登录后,何时应将用户重定向到他们自己的特定控制器/布局/视图?是否有一种方法可以在routes.rb中检查current_user.role并根据他们是项目经理还是客户来设置主页(或重定向)?这是我可以在Devise中进行的更改吗?
谢谢您提前的帮助! --马克

我曾经问过类似的问题:https://dev59.com/hlDTa4cB1Zd3GeqPLryO - 那里的答案是创建一个专门用于路由的主页控制器。你无法在routes.rb中访问Devise信息。 - Skilldrick
5个回答

41

你的routes.rb文件不知道用户扮演的角色,因此你将无法使用它来分配特定的根路由。

你可以设置一个控制器(例如passthrough_controller.rb),该控制器可以读取角色并进行重定向。类似这样:

# 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'
这样,所有用户都将有一个入口点,然后根据其角色重定向到适当的控制器/操作。

3
这是一个更好的实现方式,因为它不会仅仅为了重定向而创建一个新的控制器。此外,这也可以避免每次用户被重定向到根路径时,都需要从浏览器发送一次额外的请求到服务器。 - Khaja Minhajuddin
优雅的解决方案,不需要更改任何现有代码,即我可以像以前一样保留所有的 redirect_to root_path 语句。我很喜欢它 :) - Wolfgang
1
这对我很有效。我决定将其放入我的应用控制器中,一切似乎都很好...但是你为什么要为此设置一个全新的控制器呢?只是想知道是否有我忽略的东西。 - counterbeing
如果用户直接在URL中输入另一个路径,那么这个控制器很容易被绕过。 :( - mmsilviu

22

最简单的解决方案是使用lambda

root :to => 'project_managers#index', :constraints => lambda { |request| request.env['warden'].user.role == 'project_manager' }
root :to => 'clients#index'

它对我有用。但我有一个问题。我正在使用 Ruby 2.3.0,它允许我使用新的箭头 lambda 语法。但是,当我使用 constraints: -> { ... } 的时候,我遇到了语法错误。如果我使用 lambda 而不是 ->,就可以正常工作。你有什么想法吗? - elquimista
据说可以使用proc/lambda作为:to选项(root to: proc { |env| ...}),但是我遇到了各种问题,因为它似乎会破坏url_for。这个解决方案在不破坏url_for的情况下工作。加一! - Steve

22

另一个选项是像这样向authenticated方法传递一个proc(在此示例中,我正在使用rolify):

另一种选择是将proc传递给authenticated方法,如下所示(在本示例中,我使用的是rolify):

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"

请注意,在Rails 4中,您必须为每个根路由指定唯一名称,详情请参见此问题


让我们给这个答案投赞成票!它应该被接受为正确。 - mmike
有没有一种方法可以让用户决定路径?例如:root to: organization_path(user.organization) - thisismydesign

8

我在一个使用Warden的Rails 3应用程序中执行此操作。由于Devise是建立在Warden之上的,所以我认为它会适用于你,但在依赖它之前一定要进行一些实验。

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'

如果用户的角色是“project_manager”,则使用ProjectManagersController - 如果不是,则使用ClientsController。如果用户根本没有登录,env['warden'].user将为nil,您将会收到错误信息,因此您可能需要解决这个问题,但这将让您有一个良好的开端。

感谢您的回答!实际上,我先尝试了这个方法,但是我无法使其正常工作(可能是因为我对此不熟悉)。更何况,我现在几乎无法理解自己在做什么,因此“尝试实验性操作”这个想法就很可怕! - Mark

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接