Как сгруппировать разрешения из роли

#ruby-on-rails

#ruby-on-rails

Вопрос:

Я создаю приложение Rails 5.2. В этом приложении у меня есть объекты ролей с разрешениями (объекты JSONB). Пользовательский объект может быть назначен многим ролям.

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

 <Role id: 235134714, account_id: 1, title: "Admin", permissions: {"task_create"=>true}>
  

Я ищу способ извлечь все разрешения (в массив) из всех объектов роли, которым назначен пользователь. Критерии успешного извлечения следующие:

  1. Каждое разрешение должно отображаться только один раз в результирующем массиве.
  2. Значение «true» всегда должно быть выбрано вместо «false», если есть две роли, которые получили одинаковые разрешения, но одна из них является false, а одна или более — true.

Это мой код прямо сейчас (в котором отсутствует проверка true / false):

 def permissions
      perms = []
      self.roles.each do |role|
        perms << role.permissions unless perms.include?(role.permissions)
      end
      perms
    end
  

Я искал повсюду способ сделать это, но не смог найти рабочее решение.

Ответ №1:

Одним из возможных решений является использование each_with_object для создания хэша, а затем преобразования хэша в массив. Этот оператор ||= предпочтет true вместо false .

 permissions = self.roles.each_with_object({}) do |role, hash|
  role.permissions.each do |key, value|
    hash[key] ||= value
  end
end
permissions.map { |key, value| { key => value } }
  

Ответ №2:

Поверх всего этого написан ненужный антишаблон JSON.

Используя JSON, вы отказываетесь от всех преимуществ ActiveRecord и модели реляционной базы данных и полностью отменяете нормализацию ваших данных. Это также проблема, которую относительно легко решить с помощью двух отдельных таблиц:

 class Role < ApplicationRecord
  has_many :role_permissions
  has_many :permissions, through: :role_permissions
end


# rails g model permission name resource:belongs_to:polymorphic   
class Permission < ApplicationRecord
  has_many :role_permissions
  has_many :roles, through: :role_permissions
  belongs_to :resource, 
    polymorphic: true,
    optional: true
end

# rails g model role_permission role:belongs_to permission:belongs_to
class RolePermission < ApplicationRecord
  belongs_to :role
  belongs_to :permission
end
  

Эквивалентом создания этого разрешения было бы:

 p = Permission.find_or_create_by!(resource_type: 'Post', name: :create)
p = Permission.find_or_create_by!(resource: Post.first, name: :modify)
  

Это позволяет вам распространять разрешения на класс ресурсов или отдельный ресурс (путем добавления идентификатора). И вы можете прикрепить любое количество разрешений к роли без дублирования:

 admin = Role.create!(name: :admin)
admin.permissions.find_or_create_by!(resource_type: 'Post', name: :create)
admin.permissions.find_or_create_by!(resource_type: 'User', name: :ban)
  

Запрос разрешений выполняется просто путем запроса ассоциации:

 class Role < ApplicationRecord
  has_many :role_permissions
  has_many :permissions, through: :role_permissions

  def has_permission?(name, resource = nil)
    if resource.nil?
      permissions.where(name: name, resource: nil)
    elsif resource.is_a?(Class)
      permissions.where(name: name, resource_type: resource, resource_id: nil)
    else
      permissions.where(name: name, resource: resource)
    end.exists?  
  end
end