使用Rolify定义角色

时间:2015-11-27 04:14:55

标签: ruby-on-rails roles user-roles rolify

我正在尝试使用Rails 4制作应用程序。

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

对于其他同样问题的人来说,下面有两个非常好的答案(我只能打一个,但我用了两个)。看看lorefnon& Cyb3rDud3在下面回答)。我还在搞清楚,但已经用数组(如lorefnon节目)和控制器/路由函数(如Cyb3rDud3所示)进行了迁移。

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

如何在代码中定义角色?

该委员会的其他人提出了一些问题,这些问题暗示他们在db:seeds文件中定义了角色。我不想这样做,因为我想控制谁使用我的种子文件比谁创建角色更紧密。

你在哪里做?

所有示例都显示它是从控制台完成的。我想定义一个角色列表,然后我想给角色权限(我想在这个部分使用权威)。

我有一个用户模型。我看到的另一个宝石是角色模型。它要求您在用户模型中创建角色数组。很明显,你应该在Rolify中做到这一点 - 没有一个文件能给你这一步吗?

您在哪里定义角色?

4 个答案:

答案 0 :(得分:8)

  

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

Rolify文档不使用控制台来定义角色 - 它演示了如何在纯ruby中添加角色。这是非常强大的,因为你可以 定义角色,你可以运行ruby。您不限于在某些配置文件或某个数据库表中定义的静态角色列表。

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

最常见的用例分为两组:

<强> 1。角色是静态的。

应用程序安装/部署期间应用程序开发人员,支持人员或公司高管创建一次角色,并且在正在运行的应用程序的生命周期内,这些预先创建的角色将分配给不同的用户。

此类角色的最常见用例包括对公司中人员的设计进行建模(开发人员,经理,支持等),或对先前已知职责进行建模(编辑,管理员,查看者等)。

如果你的角色确实属于这种用例,那么接下来你必须决定 - 它的职责是创建和修改角色。通常有两种可能性:

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

迁移的优势在于,您可以根据需要轻松回滚数据。此迁移是rolify生成器生成的迁移的补充,它为您创建角色相关表的模式(请参阅下图)。

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

分贝/迁移/ 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

有些人rightly consider它是一个反模式,可以通过迁移管理架构更改和数据更改。 data-migrate是一个gem,允许您将以数据为中心的迁移与模式迁移分开。

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

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

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

<强> 2。角色是动态的

此类别涵盖角色被视为更类似于类别或标签并且可由最终用户创建/修改/删除的用例。例如,图书馆员可以为特定类型的书籍分配一些角色/类别。

这种情况类似于1.2,因为您必须通过rails控制器处理创建/删除/更新角色。

下一部分是如何在表格中构建信息。

Rolify期望一个特定的模式(在某种程度上可自定义),但预期的模式足够灵活,可以处理所有上述用例。

Rolify tables

答案 1 :(得分:5)

我确实刚刚经历了相同的过程,就像@ user2860931一样,我所能找到的就是如何从控制台分配角色的一些例子。我需要的是一种以编程方式灵活的方式来管理具有管理角色的用户或具有pmo角色的用户如何将这些角色分配给其他人。

通过一些实验,这就是我为我解决的问题。在这个例子中,我使用Devise进行身份验证,并使用Rolify作为角色。

我假设您已经安装并运行了Devise,因此您拥有现有的用户模型。按照gem页面上的说明安装Rolify。我使用了角色模型的建议名称Role。所以按照这里所说的做一切:https://github.com/RolifyCommunity/rolify。安装GEM,使用rolify角色用户生成。并迁移数据库迁移。

这将有效地为您留下一个新的表Roles和与Users表的has_and_belongs_to_many关系。

至于我的目的,我不需要角色的通常创建读取(显示)更新删除(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路线时,你将获得应用程序的通常路径,使控制器工作。像这样按顺序:

                  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和一个编辑,您可以在其中分配和删除现有角色。像这样。

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 %>

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

Example of user record and role pick list

对我来说这对我来说也是一个小问题,但希望这会让你的方式增加逻辑和用户体验。

答案 2 :(得分:4)

在阅读了项目页面中的文档和示例后,我决定不使用gem来管理我页面中的角色,因为我认为这需要花费大量时间来配置和使用。所以相反,我做了以下,(我相信您使用了设计用于您的用户模型,即使它不是强制性的):

如果您想要定义某些角色并且“静态”不可修改但可从您的页面分配,请参见下文,如果没有跳转到下一个粗线

  1. 通过迁移向您的role: integer模型添加名为User的字段。 (我们使用整数,因为这个值将与我们在下一步中定义的枚举中的一个条目相关联)
  2. 在您的文件user.rb(模型)中,添加如下所示的enum

    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. 然后在任何控制器或视图或def中,您只需要获得当前用户或您想要分配角色的任何用户,并按照以下行进行操作:

    #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. 然后,您只需询问用户的角色,即可在您正在处理的任何页面或控制器中进行自己的验证:

    #see if the current user is admin
    if current_user.role == "admin"
      #do some admin stuff
    end    
    
  5. 如果您想在页面中添加,修改和删除角色,并在其中分配

    首先,在直接从页面分配角色时要小心。这里的问题是每个人都能够在一个领域中为自己分配自己的角色。但如果这是您所需要的,请按以下步骤操作:

    1. 创建名为Role的模型,其中包含字段role: string
    2. 创建与该模型关联的控制器roles_controller.rb
    3. 创建所需的视图,以便显示与角色管理相关的操作(创建,编辑,删除)请注意,此页面必须仅供登录用户使用。为用户选择或添加角色,您将在用户控制器或任何其他所需控制器中进行管理。
    4. 在您想要询问用户角色的任何其他视图中,您需要访问角色表并检索与该用户对应的角色。 users表将需要一个列role_ids: text (它的文本因为您需要在那里保存多个角色,所有角色都包含在一个数组中),这将代表他的角色。在user.rb模型中,您可以使用get_roles和其他def方法在控制器和视图中使用更清晰的代码:

      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. 最后,您当然需要为角色,创建,更新和销毁以及与其观点相关联的所有def实施控制器。

    6. 您可以查看how to save arrays in database, serialize

      此方法不会使用与市场中的角色管理或授权相关的任何宝石:punditcancanrolify。如果你对我的方法持怀疑态度,想要弄清楚你的手,请留下一些链接。

答案 3 :(得分:0)

@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。