使用Rolify定义角色

7

我正在使用Rails 4制作一款应用程序。

我正在研究角色管理,并希望使用Rolify,因为它支持实例级别的角色分配。

对于其他人遇到的同样问题,下面有两个非常好的答案(我只能选择一个,但我都使用了)。请查看lorefnon和Cyb3rDud3的答案。我仍在思考,但已经创建了一个包含数组的迁移(如lorefnon所示)和控制器/路由函数(如Cyb3rDud3所示)。

完全使我感到困惑的是,Rolify gem的所有文档都使用控制台定义角色。

我该如何在我的代码中定义角色?

该论坛上的其他人也提出了问题,暗示他们在db:seeds文件中定义了角色。我不想这样做,因为我想更紧密地控制谁使用我的种子文件,而不是谁可以创建角色。

你在哪里做?

所有的例子都显示从控制台完成。我想定义一个角色列表,然后我想给角色权限(我想使用pundit进行此部分)。

我有一个用户模型。我查看过另一个gem,即角色模型。它要求您在用户模型中创建一个角色数组。这样做在Rolify中是如此明显,以至于没有一个文档给出了这一步骤?

你在哪里定义角色?

4个回答

13
“让我完全困惑的是,Rolify gem 的所有文档都使用控制台来定义角色。”
“Rolify 文档并没有使用控制台来定义角色 - 它演示了如何在纯 Ruby 中添加角色。这非常强大,因为您可以在任何可以运行 Ruby 的地方定义角色。您不受限于某个配置文件或数据库表中定义的静态角色列表。”

你需要先问的第一个问题是角色何时创建?

最常见的用例分为两组:

1. 角色是静态的。

角色在应用安装/部署期间由应用程序开发人员、支持人员或公司高管创建,而在运行应用程序的生命周期中,这些预先创建的角色被分配给不同的用户。

此类角色的最常见用例包括建模公司人员的职位(开发人员、经理、支持等),或建模预先知道的职责(编辑、管理员、查看器等)。

如果您的角色符合这些用例,下一步您需要决定的是,谁负责创建和修改角色。通常有两种可能性:

1.1. 应用程序开发人员本人是必须添加/删除/修改角色的人。在这种情况下,最好依赖于种子数据或Rails迁移。

迁移的优点是,如果需要,您可以轻松回滚数据。此迁移是由rolify生成器生成的迁移之外的额外迁移,该生成器为您创建与角色相关表的架构(请参阅下面的图表)。

这样的迁移可能如下所示:

db/migrate/20151204083556_create_application_roles.rb

class CreateApplicationRoles < ActiveRecord::Migration
  def up
    ['admin', 'support', 'editor'].each do |role_name|
      Role.create! name: role_name
    end
  end
  def down
    Role.where(name: ['admin', 'support', 'editor']).destroy_all
  end

end

有些人正确地认为,将模式更改和数据更改都由迁移管理是一种反模式。data-migrate是一款 gem 工具,允许您将数据中心的迁移与模式迁移分开。

在这种情况以及下面所有情况中,实际角色的分配将基于用户操作或应用程序事件通过 rolify 提供的 add_roleremove_role 方法进行。这将在运行应用程序的生命周期中发生,而不是在应用程序安装期间发生。

1.2 添加/删除/修改角色的任务由支持团队或技术执行人员完成。在这种情况下,需要提供一个管理角色的管理界面。

在这种情况下,您将拥有一个 Rails 控制器来管理角色。创建操作将用于创建角色,显示操作将用于呈现角色等。这些操作将具有相应的视图,为最终用户提供图形用户界面以管理角色。

2. 角色是动态的

这个类别涵盖的用例更像是角色被视为类别或标签,可以由终端用户创建/修改/删除。例如,图书管理员可以将某些角色/类别分配给特定类型的图书。
这种情况与1.2类似,因为您需要通过Rails控制器处理创建/删除/更新角色。
下面是关于表格中信息结构的内容。
Rolify期望有一个特定的模式(在一定程度上可自定义),但预期的模式足够灵活,可以处理以上所有用例。

Rolify tables


嗨,那么我该如何设置一组可以在我的应用程序中分配的角色?有没有关于如何做到这一点的指南?角色由某些其他用户(具有 pundit 权限来分配它们的用户)分配给用户。管理员创建角色并委派分配权限。我正在尝试弄清楚如何创建这些角色。Gem 文档显示如何在控制台中执行此操作,但我希望它们在我的应用程序中可用。我无法弄清楚如何做到这一点。 - Mel
哦,我不明白。你的意思是在迁移中定义属性(作为字符串),并使用我想在应用程序中使用的角色名称吗? - Mel
“On the fly” 是什么意思?我真的很想理解你所表达的意思。我认为 rolify 可能更适合那些具有深入 Ruby 知识的人,但我希望使用它,因为我想要的角色设置(角色模型 gem 不够用)。虽然这两个 gem 之间的一个区别是 role model 显示如何定义角色。 - Mel
我已经更新了问题并进行了更详细的阐述。如果有帮助,请告诉我。 - lorefnon
感谢Lorefnon。我的迁移没有up/down选项(它们有change)。我认为你想说的是,不是在迁移中列出每个角色(使用布尔触发器来表示用户是否具有该角色),而是像你在迁移中所做的那样,创建一个方法并在其中列出一组角色。我以前从未这样使用过迁移,但我会尝试一下。 - Mel
显示剩余3条评论

7

我刚刚经历了同样的过程,并且像@user2860931一样,我只能找到一些控制台中如何分配角色的示例。我需要的是以编程方式将具有管理员角色或PMO角色的用户分配这些角色给他人的灵活方法。

通过一些实验,这就是我为自己解决问题的方法。在此示例中,我正在使用Devise进行身份验证,并使用Rolify进行角色管理。

假设您已经安装并使用了Devise,因此您已经拥有现有的用户模型。按照宝石页面上的指示安装Rolify。我使用Role作为角色模型的名称。因此,请按照此处指定的所有步骤操作:https://github.com/RolifyCommunity/rolify。安装宝石,使用rolify生成Role User。然后迁移数据库迁移。

这将有效地使您拥有一个新表Roles,并带有与Users表的has_and_belongs_to_many关系。

对于我的目的,我不需要通常的Create Read(show)Update Delete(CRUD)接口来管理角色,我只是通过seeds.rb创建了一些角色,如下所示。

#Seeding the Role table
#
p "Removing existing #{Role.all.count} roles"
Role.destroy_all
p "Creating 7 roles"
[:user, :admin, :portfolio_manager, :programme_manager,     :project_manager, :coordinator, :pmo].each do |role|
  Role.create( name: role )
end
p "Should have created 7 Roles, roles created: #{Role.all.count}"

我将开发时的附加注释保留,以便一目了然。因此,当您运行
rake db:seed
时,你会设置一些角色。 或者,您可以像通常那样创建控制器和视图来允许具有管理员角色的用户添加新角色。
现在就开始吧。到目前为止,Devise已经完成了有关用户的所有工作,或者您已经自己编写了控制器,但是您需要创建自己的用户控制器和视图。 我只想要一个针对每个用户的角色列表的复选框,所以我按以下方式完成了它。这是我的
users_controller.rb

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update]

  def index
    @users = User.all
  end

  def show
  end

  def edit
  end

  def update
    respond_to do |format|
      if @user.update(user_params)
        # TODO: Move hardcode flash message into language file
        format.html { redirect_to @user, notice: 'User was successfully updated.'}
        format.json { render :show, status: :ok, location: @user }
      else
        format.html { render :edit }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(:username, :email, {role_ids: []})
  end
end

现在你需要定义这些路由,以便它们不会与Devise的路由冲突。我目前将其简化并将所有常规路由资源化,为了使您的应用程序更加安全,您可能需要更改它,并仅允许实际拥有的路由。
routes.rb

Rails.appliction.routes.draw do
  devise_for :users
  root 'pages#home'
  resources :users    #must be after devise
end

当你现在运行 rake routes 命令时,会得到应用程序所需的常规路径,以使控制器正常工作。如下所示,并依照该顺序排列:
                  Prefix Verb   URI Pattern                      Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
             user_unlock POST   /users/unlock(.:format)        devise/unlocks#create
         new_user_unlock GET    /users/unlock/new(.:format)    devise/unlocks#new
                         GET    /users/unlock(.:format)        devise/unlocks#show
                    root GET    /                              pages#home
                   about GET    /about(.:format)               pages#about
                 contact GET    /contact(.:format)             pages#about
                   users GET    /users(.:format)               users#index
                         POST   /users(.:format)               users#create
                new_user GET    /users/new(.:format)           users#new
               edit_user GET    /users/:id/edit(.:format)      users#edit
                    user GET    /users/:id(.:format)           users#show
                         PATCH  /users/:id(.:format)           users#update
                         PUT    /users/:id(.:format)           users#update
                         DELETE /users/:id(.:format)           users#destroy

现在剩下的就是构建用户界面,这对我来说是最重要的部分,如果我理解正确,你也是这样认为的。为了快速构建,我的示例尚未完全使用css进行美化,但我相信您可以按照自己的喜好完成。因此,为了显示现有用户并从列表中选择他们,请在/app/views/users文件夹中创建index.html.erb。创建一个简单的show.html.erb和一个edit,在其中可以分配和删除现有角色。就像这样。
index.html.erb

<!-- TODO: Tidy up this file and make it look good -->
<!-- TODO: Remove hard coded text to a locale file -->
<% @users.each do |user| %>
  <p>
    <%= link_to "#{user.username}<#{user.email}>", user %>
    <%= link_to "edit", edit_user_path(user) %>
  </p>
<% end %>

show.html.erb

<!-- TODO: Tidy up this file and make it look good -->
<!-- TODO: Remove hard coded text to a locale file -->
<p>
  Username: <%= @user.username %>
</p>
<p>
  Email address: <%= @user.email %>  
</p>

<%= link_to "Back", users_path %>

edit.html.erb

<!-- TODO: Tidy up this file and make it look good -->
<!-- TODO: Remove hard coded text to a locale file -->
<p>
 Username: <%= @user.username %>
</p>
<p>
 Email address: <%= @user.email %>
</p>

<%= form_for @user do |f| %>
  <% Role.all.each do |role| %>
    <%= check_box_tag "user[role_ids][]", role.id, @user.role_ids.include?(role.id) %>
    <%= role.name %></br>
  <% end %>
  <%= f.submit %>
<% end %>

<%= link_to "Back", users_path %>

这里有一个简单的用户界面,它列出了数据库中所有可用的角色,并在用户记录旁提供复选框,以启用或禁用此类角色。就像这样:

用户记录和角色选择列表示例

这对我来说也是一个小难题,但希望这能帮助您增加逻辑和用户体验。


嗨,现在我真的很困惑。如果你不给角色一个属性类型,那么你如何将它们放入表格中呢?我在考虑角色表应该有像"学生:布尔值"这样的字段,然后如果用户是学生,就将其设置为true。如果不是通过这种方式定义表中的内容,那么应该在哪里定义呢? - Mel
你是否已经安装了 rolify?为什么不快速创建一个新的 Rails 应用程序作为测试项目,以便您可以检查它。您会发现布尔值并不重要,它是一个 has_and_belongs_to_many 关系。角色名称存储为字符串。关系通过外键维护。 - Cyb3rDud3
不需要的。安装 gem 并运行其生成器时,它会为您创建表的迁移。您不应自己创建。Juanm 包含的示例是使用迁移过程,而我使用种子文件。纯粹将“静态”角色输入到数据库中。任何方法都可以。那么,您是否已经基于设备创建了工作的用户身份验证系统并创建了自己的控制器以便您可以列出用户? - Cyb3rDud3
我甚至都不理解那个。有适合初学者的教程吗? - Mel
顺便说一句,如果你的项目在GitHub上,我很乐意看一下,并向你展示一个带有角色上下文的分支。这几乎不需要任何时间...祝你好运。 - Cyb3rDud3
显示剩余10条评论

4

在阅读了他们项目页面中的文档和示例后,我决定不使用gem来管理我的页面中的角色,因为我认为这需要耗费很多时间来配置和使用。所以,我做了以下操作(即使它并非强制要求,我相信你在用户模型中使用了devise):

如果您想定义某些角色,并希望这些角色是“静态”的、不可修改但可从您的页面分配,请参见下面的内容;否则,请跳到下一个粗体

  1. Add a field called role: integer to your User model with a migration. (we use integer cause this value will be associated with one entry in the enum we define in the next step)
  2. In your file user.rb (the model), add an enum like below:

    class User < ActiveRecord::Base
      devise :registerable, #...
    
      enum role: [:admin, :normal, :premium, :moreRolesHere ]
      after_initialize :set_default_role, :if => :new_record?
    
      def set_default_role
        self.role ||= :normal
      end
    
    end
    
  3. Then in any controller or view or def you just have to get the current user or any user you want to assign a role and do as simple as the line below:

    #let's suppose we want to make premium the current user
    current_user.premium!
    
    #or someone else to be admin
    user = User.first
    user.admin!
    
  4. Then you can make your own validations in any page or controller you are working on, just by asking the role of the user:

    #see if the current user is admin
    if current_user.role == "admin"
      #do some admin stuff
    end    
    

如果您想在页面中添加、修改和删除角色,并将它们分配到其中

首先,当您直接从页面上分配角色时,请小心。问题在于每个人都可以在字段中选择自己的角色。但如果这正是您需要的,请按照以下步骤操作:

  1. Create a model called Role, with a field role: string
  2. Create the controller associated to that model, roles_controller.rb
  3. Create the views you need in order to display the actions related to the management of the roles (create, edit, delete) be careful that this pages have to be only available to signed in users. The selection or addition of a role to a user you will manage inside your users controller or any other controller you want.
  4. In any other view you want to ask for the role of a user you will need to access the roles table and retrieve the one(s) corresponding to the user. The users table will need a column role_ids: text (it's text cause you need multiple roles to be saved there, all roles wrapped in an array), which will represent his roles. In your user.rb model you could have a get_roles and other defs methods to have cleaner code in controllers and views:

    class User < ActiveRecord::Base
      devise :registerable, #...
    
      serialize :role_ids
    
      #will return you an array of roles-(strings) of the user
      def get_roles
        roles = []
        role_ids.each do |role_id|
          roles << Role.find(role_id).role
        end
        return roles      
      end
    
      #ask if this user has some role-(string)
      def has_role(role)
        roles = get_roles
        return roles.include?(role)
      end
    
    end
    
  5. Lastly, of course you will need to implement the controller for roles, the create, update and destroy and all the defs associated to their views.

你可以参考如何将数组保存在数据库中,使用序列化的方法
这种方法不使用任何与角色管理或授权相关的gem,例如市场上的punditcancanrolify。如果你对我的做法持怀疑态度,想了解更多信息,请查看这些链接。

1

@lorefnon的被接受的答案在迁移中包含了一个恶性反模式:

class CreateApplicationRoles < ActiveRecord::Migration
  def up
    ['admin', 'support', 'editor'].each do |role_name|
      Role.create! name: role_name
    end
  end
  def down
    Role.where(name: ['admin', 'support', 'editor']).destroy_all
  end
end

不是因为数据和模式原则之间的分离,而是因为我们在迁移中永远不应该使用模型对象!

迁移是不可变的,但模型是可变的:如果您的一个或多个迁移依赖于一个或多个模型类,那么如果其中一个类被修改、重命名或删除(例如,如果我们决定删除我们的依赖关系中的 rolify),它可能很容易被破坏。

迁移应仅依赖于 ActiveRecord::Migration 机制(add_column、create_tables 等)和纯 SQL。


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