Реализация нескольких пользовательских ролей

У меня был большой успех с помощью state_machine и я люблю методы класса, которые он динамически создает через несколько строк кода.

Однако я не уверен, как продолжить работу с создаваемой системой. В настоящее время я разрабатываю систему, в которой пользователи имеют много ролей. Таким образом, это не так просто, как наличие пользователя, состояние которого от неподтвержденного до подтверждено, а затем потенциально возможно admin.

Теперь пользователь имеет много ролей и может быть потенциальным, велосипедистом, координатором, manger strong > , форум admin, администратор магазина, супер админ и сборщик средств.

Итак, иерархия выглядит следующим образом:

суперамин

форум admin, магазин admin

велосипедист, координатор, 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) Даже если я делаю это правильно, я бы хотел создать DSL state_machine-esque, поэтому, когда мне нужно создать новую роль, скажем, "бегун", я могу просто сделайте что-то подобное в моей модели:

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_superadmins, only_cyclists ect и я вызываю их с помощью метода before_method в разных субконтроллерах.

Это нормально? Должен ли я использовать канкан или что-то еще?

Если я делаю это правильно, мне интересно, как я должен динамически создавать эти методы с помощью 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 ONLY empty when any of the current_user roles exists in the wanted_roles array
end

Мне просто нужны некоторые указатели:)

Как создать драгоценный камень, чтобы сделать доступным для модели allowed_roles метод и multiroles для модели пользователя?

Может ли cancan управлять несколькими ролями, как это? Должен ли я просто использовать это?

Ответ 1

Возобновленный ответ:

Для обработки ролей для вашей модели хорошие варианты - использовать gem rolify. С его помощью вы можете легко определить столько ролей, сколько хотите, и связать столько роли, сколько хотите для своего Пользователя. Он прост в использовании, просто следуйте официальной документации здесь.

CanCan (или его sucessor CanCanCan) используется для обработки разрешений. Вы определите, какой пользователь должен выполнять каждую роль (определенную с помощью ролика) в файле app/models/ability.rb. Затем в контроллерах или представлениях вы просто проверяете, разрешено ли пользователю выполнять действие для ресурса. Например, в вашем контроллере вы подтверждаете авторизацию, например @comment = Comment.new(params); authorize! :create, @comment, и в своем представлении вы подтверждаете авторизацию как if can? :create, Comment. Refuer к официальной документации здесь, чтобы узнать, как настроить и использовать CanCan.

Полагая это на вашу конкретную проблему:

Добавьте в свой Gemfile файл Rolify (gem "rolify") и CanCan (gem "cancan").

Выполните команду оболочки rails rails g rolify Role User, чтобы создать новый класс с именем Role (или используйте имя, которое вы предпочитаете), и добавьте некоторые методы класса в ваш существующий класс User. Поскольку новая роль класса добавит роль таблицы в вашу базу данных, вам нужно запустить rake db:migrate (при использовании ActiveRecord).

Добавьте resourcify в любой класс, к которому будет обращаться Пользователь. Например:

class Forum < ActiveRecord::Base
  resourcify
end

После того, как вы выполнили эти шаги, ваш пользовательский класс будет оснащен методами add_role, remove_role и has_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

Итак, вы можете написать свой класс User следующим образом:

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

Надеюсь, это поможет вам решить вашу проблему.