#ruby-on-rails
#ruby-on-rails
Вопрос:
Я создаю приложение Rails 5.2. В этом приложении у меня есть объекты ролей с разрешениями (объекты JSONB). Пользовательский объект может быть назначен многим ролям.
Объект роли выглядит следующим образом:
<Role id: 235134714, account_id: 1, title: "Admin", permissions: {"task_create"=>true}>
Я ищу способ извлечь все разрешения (в массив) из всех объектов роли, которым назначен пользователь. Критерии успешного извлечения следующие:
- Каждое разрешение должно отображаться только один раз в результирующем массиве.
- Значение «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