Как структурировать ассоциацию has_many с динамической областью действия? - PullRequest
0 голосов
/ 15 мая 2019

У меня есть таблица пользователей в моей базе данных. Пользователь может быть либо типа «администратор» или «менеджер».

Учитывая приведенные ниже модели и схемы, мне бы хотелось, чтобы для каждого экземпляра пользователя 'manager' пользователь 'admin' мог выбрать одно, несколько или все местоположения арендатора, к которому принадлежит менеджер, чтобы выбрать, какой местоположения, которые менеджер может контролировать.

Мои модели

class User < ActiveRecord::Base
  belongs_to :tenant
class Tenant < ActiveRecord::Base
  has_many :users, dependent: :destroy
  has_many :locations, dependent: :destroy
class Location < ActiveRecord::Base
  belongs_to :tenant, inverse_of: :locations

Я пробовал два пути

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

Второй , настройка атрибута Control_Locations в таблице пользователей. Затем я настроил некоторый код, чтобы пользователь «admin» мог выбирать, какие местоположения может контролировать «manager», заполняя его атрибут «control_locations». Однако то, что сохраняется в базе данных (внутри массива Control_Locations), это строки, а не экземпляры местоположений.

Вот код, который я пробовал для второго пути:

Миграция

def change
  add_column :users, :controlled_locations, :string, array: true, default: []
end

На виду

= f.input :controlled_locations, label: 'Select', collection: @tenant_locations, include_blank: "Anything", wrapper_html: { class: 'form-group' }, as: :check_boxes, include_hidden: false, input_html: {multiple: true}

В пользовательском контроллере (внутри метода обновления)

if params["user"]["controlled_locations"]
  params["user"]["controlled_locations"].each do |l|
    resource.controlled_locations << Location.find(l.to_i)
  end
  resource.save!
end

Что я ожидаю

Прежде всего, я не совсем уверен, что второй путь, который я пробовал, - это хороший подход (хранение массивов в БД). Так что мой лучший выбор - это создать ассоциацию с определенными границами, если это возможно.

В случае, если возможен второй путь, я хотел бы получить что-то вроде этого. Допустим, войдя в Admin, я выбрал, что пользователь с ID 1 (менеджер) может управлять одним местоположением (Бостонский стадион):

user = User.find(1)
user.controlled_locations = [#<Location id: 55, name: "Boston Stadium", created_at: "2018-10-03 12:45:58", updated_at: "2018-10-03 12:45:58", tenant_id: 5>]

Вместо этого я получаю следующее:

user = User.find(1)
user.controlled_locations = ["#<Location:0x007fd2be0717a8>"]

Вместо экземпляров местоположений в массиве сохраняются простые строки.

Ответы [ 2 ]

0 голосов
/ 15 мая 2019

Обычным шаблоном, используемым для авторизации, являются роли:

class User < ApplicationRecord
  has_many :user_roles
  has_many :roles, through: :user_roles

  def add_role(name, location)
    self.roles << Role.find_or_create_by(name: name, location: location)
  end

  def has_role?(name, location)
    self.roles.exists?(name: name, location: location)
  end
end

# rails g model role name:string
# make sure you add a unique index on name and location
class Role < ApplicationRecord
  belongs_to :location
  has_many :user_roles
  has_many :users, through: :user_roles
  validates_uniqueness_of :name, scope: :location_id
end

# rails g model user_role user:references role:references
# make sure you add a unique compound index on role_id and user_id
class UserRole < ApplicationRecord
  belongs_to :role
  belongs_to :user
  validates_uniqueness_of :user_id, scope: :role_id
end

class Location < ApplicationRecord
  has_many :roles
  has_many :users, through: :roles
end

Сделав систему немного более общей, чем, скажем, ассоциативная связь control_locations, вы можете повторно использовать ее для разных случаев.

Допустим, войдя в систему Admin, я выбрал, что пользователь с ID 1 (менеджер) может контролировать одно местоположение (Бостонский стадион)

User.find(1)
    .add_role(:manager, Location.find_by(name: "Boston Stadium"))

В реальных условиях MVC вы можете сделатьэто путем установки ролей как вложенного ресурса, который можно CRUD'ить, как и любой другой ресурс.Редактирование нескольких ролей в одной форме можно выполнить с помощью accepts_nested_attributes или AJAX.

Если вы хотите охватить запрос наличием роли, то присоединитесь к таблице ролей и пользовательских ролей.:

Location.joins(roles: :user_roles)
        .where(roles: { name: :manager })
        .where(user_roles: { user_id: 1 })

Чтобы аутентифицировать один ресурс, вы должны сделать:

class ApplicationController < ActionController::Base
  protected 
  def deny_access
    redirect_to "your/sign_in/path", error: 'You are not authorized.'
  end
end

class LocationsController < ApplicationController
  # ...
  def update
    @location = Location.find(params[:location_id])
    deny_access and return unless current_user.has_role?(:manger, @location)
    # ...
  end
end

Вместо того, чтобы использовать собственную систему авторизации, хотя я хотел бы использовать rolify и пандит .

0 голосов
/ 15 мая 2019

Во-первых, в вашем коде отсутствует ассоциация locations в классе Tenant.

class Tenant < ActiveRecord::Base
  has_many :users, dependent: :destroy
  has_many :locations

Допустим, переменная manager имеет запись User. Тогда местоположения, которыми он может управлять:

manager.tenant.locations

Если вы хотите, вы можете сократить это с помощью оператора делегата.

class User < ActiveRecord::Base
  belongs_to :tenant
  delegate :locations, to: :tenant

тогда вы можете позвонить с помощью

manager.locations
...