实现多个用户角色

时间:2014-05-10 20:17:16

标签: ruby-on-rails

我使用state_machine取得了巨大的成功,并且喜欢它通过几行代码动态创建的类方法。

但是,我不确定如何继续使用我正在创建的系统。我目前正在开发一个用户有很多角色的系统。因此,让用户的状态从未经证实已确认然后可能管理员并非如此简单。

用户现在有很多角色,可以是潜在骑车人 coorinator manger 论坛管理员商店管理员超级管理员筹款人

所以层次结构是这样的:

  

超级管理员

     

论坛管理员商店管理员

     

骑单车协调员经理筹款人

     

潜在

但是,有一台状态机不会在这里删除它,因为一个用户可以完全同时拥有上述所有角色。

我正在实现我自己的类方法,以便在某种程度上模拟状态机:

class User < ActiveModel::Base
    has_many :jobs
    has_many :roles, through: :jobs

    def role_array
        self.roles.pluck(:role)
    end

    def has_role?(role)
        role_array.include?(role)
    end

    # checking
    def is_superadmin?
        role_array.include?('superadmin')
    end


    # changing
    def add_role(role)
       self.update_attributes(accepted_at: Time.now) if self.is_only_potential?
       self.user_roles.create(role_id: Role.find_by(role: role).id ) if !self.has_role?(role)
    end

    def remove_role(role)
        self.user_roles.find_by( role_id: Role.find_by(role: role).id ).destroy if self.has_role?(role)
    end

    def make_superadmin!
        add_role('superadmin')
    end

    def denounce_superadmin!
        remove_role('superadmin')
    end

end

这只是一点点。所以我的问题是:

1)我做得对吗?您将如何处理具有多个角色的用户?

2)即使我做得对,我也想创建一个state_machine-esque DSL,所以当我需要创建一个新角色时,让我们说&#39; runner&#39;,我可以在我的模型中做这样的事情:

class User < ActiveModel::Base
    has_many :jobs
    has_many :roles, through: :jobs

    multiroles initial: :potential do
        roles [:superadmin, :forum_admin, :store_admin, :cyclist, :coordinator, :manager, :fundraiser, :potential]
         # dynamically creates the above methods for getting and setting for all roles
    end

我应该如何创建多重方法?在lib内?准备好作为我的第一个宝石打包?

我不知道如何动态创建方法,但我想开始:)

只是想一想,也许multiroles方法可以通过Roles.all动态获取所有角色,并自动添加上述方法!甚至可以照顾has_many :jobs has_many :roles, through: :jobs

另外,我应该如何验证这些角色?我目前在控制器的前一块中执行此操作:

def only_superadmins
    redirect_to root_url if !current_user.has_role?('superadmin')
end

我的应用程序控制器only_superadminsonly_cyclists中也有一堆这样的方法,我在各个子控制器中通过before_method方法调用它们。

这可以吗?我应该使用cancan还是什么?

如果我做得对,我想知道如何使用我的Gem动态创建这些方法。我正在思考这些问题:

class panel_controller < ApplicationController   
    allowed_roles [:super_admin, :forum_admin, :store_admin]
end

并且allowed_roles方法将创建这些方法

def allowed_roles(role_array)
    role_array.each do |role|
         define "only_#{role.to_s}s" do |arg|
            redirect_to root_url if !current_user.has_role?(arg.to_s)
         end
    end
end

所以这会以编程方式创建这些方法:

def only_super_admins
    redirect_to root_url if !current_user.has_role?('super_admin')
end

def only_forum_admins
    redirect_to root_url if !current_user.has_role?('forum_admin')
end


def only_store_admins
    redirect_to root_url if !current_user.has_role?('store_admin')
end

虽然我不明白为什么那不起作用,但这并不会让我觉得效率太高。

也许allowed_roles应该是这样的:

def allowed_roles(wanted_roles)
    redirect_to root_url unless (current_user.role_array & wanted_roles).empty? # it's ONLY empty when any of the current_user roles exists in the wanted_roles array
end

我只是想要一些指示:)

如何创建gem以使控制器可以使用allowed_roles方法并且用户模型可以使用multiroles

可以cancan管理这样的多个角色吗?我应该只使用它吗?

1 个答案:

答案 0 :(得分:17)

恢复回答:

为了处理模型的角色,一个很好的选择是使用gem rolify 。有了它,您可以轻松定义所需的任意角色,并将您想要的多个角色关联到您的用户。它使用简单,只需遵循官方文档here

CanCan (或其成员 CanCanCan )用于处理权限。您将定义具有每个角色的用户(使用rolify定义)在文件app/models/ability.rb中有权执行的操作。然后,在控制器或视图中,您只需验证用户是否有权对资源执行操作。例如,在您的控制器中,您可以验证@comment = Comment.new(params); authorize! :create, @comment之类的授权,并在您的视图中验证授权,例如if can? :create, Comment。请参阅官方文档here,了解如何设置和使用CanCan。

针对您的具体问题:

将Rolify(gem "rolify")和CanCan(gem "cancan")宝石添加到您的Gemfile中。

执行rails shell命令rails g rolify Role User以创建名为Role的新类(或使用您喜欢的名称),并在现有类User中添加一些类方法。由于新类Role将向数据库添加表Role,因此必须运行rake db:migrate(使用ActiveRecord时)。

resourcify添加到用户将访问的任何类。例如:

class Forum < ActiveRecord::Base
  resourcify
end

完成这些步骤后,您的用户类将配备方法add_roleremove_rolehas_role,您可以使用它们添加任意数量的角色:

user.add_role :superadmin
user.add_role :fundraiser

user.has_role? :superadmin
# >> true
user.has_role? :fundraiser
# >> true

您甚至可以将角色范围限定为一个资源或实例:

user.add_role :cyclist, Forum
user.add_role :coordinator, Forum.first

user.has_role? :cyclist, Forum
# >> true
user.has_role? :cyclist, Store
# >> false
user.has_role? :coordinator, Forum.first
# >> true
user.has_role? :coordinator, Forum.second
# >> false

所以你可以像这样编写你的用户类:

class User < ActiveModel::Base
  rolify
  has_many :jobs

  # checking
  def is_superadmin?
      self.has_role?('superadmin')
  end

  # changing
  def add_new_role(role)
     self.update_attributes(accepted_at: Time.now) if self.is_only_potential?
     self.add_role(role)
  end

  def make_superadmin!
      add_new_role('superadmin')
  end

  def denounce_superadmin!
      remove_role('superadmin')
  end
end

要验证这些角色,您可以使用CanCan。执行rails shell命令rails g cancan:ability以生成文件app/models/ability.rb,您将在其中定义角色的权限。

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user

    if user.has_role? :superadmin
      can :manage, All  # can manage (Read, Create, Update, Destroy, ...) everything
    elsif user.has_role? :forum_admin
      can :manage, Forum  # can manage (Read, Create, Update, Destroy, ...) any Forum
    elsif user.has_role? :store_admin
      can :manage, Store do |store|  # Can manage only its own store
        store.try(:user) == user
      end
    elsif user.has_role? :forum_member
      can :create, Post do |post|
        if post.forum.members.include? user
          true
        end
      end
      can :destroy, Post do |post|
        post.try(:user) == user
      end
      can :update, Post do |post|
        post.try(:user) == user
      end
    elsif ...

    else # Users without role
      can :read, All
    end
  end
end

在您的控制器中,您可以调用authorize!方法。例如:

# app/controllers/posts_controller.rb
def create
  @post = Post.new(params[:post])
  @post.user = current_user
  authorize! :create, @post
  if @post.save
    redirect_to @post
  else
    render :action => 'new'
  end
end

或者您可以在控制器的开始处包含faloowing,并且在每个操作之前资源会自动加载和授权(或不授权):

class PostController < ApplicationController
  load_and_authorize_resource :post

  ...

def create
  authorize! :create, @post
  if @post.save
    redirect_to @post
  else
    render :action => 'new'
  end
end

RailsCast 上观看本教程,了解CanCan。

我希望这有助于指导您解决问题。