Activerecord has_and_belongs_to_many с вложенными группами - PullRequest
0 голосов
/ 06 февраля 2019

Мне нужно хранить группы пользователей в других группах, чем-то вроде Windows Active Directory.

У меня есть следующее, что работает

ActiveRecord::Schema.define do
  create_table :users do |table|
    table.column :name, :string
  end
  create_table :groups do |table|
    table.column :name, :string
  end
  create_join_table :users, :groups do |t|
  end
end

class User < ActiveRecord::Base
  has_and_belongs_to_many :groups
end

class Group < ActiveRecord::Base
  has_and_belongs_to_many :users
  # has_and_belongs_to_many :groups
end

peter  = User.create(id: 1, Name: 'Peter')
thomas = User.create(id: 2, Name: 'Thomas')
inf = Group.create(id: 1, Name: 'Informatics')

peter.groups << inf
inf.users << thomas
p User.find_by(Name: 'Peter').groups
p Group.find_by(Name: 'Informatics').users

Но я хотел бы также сделать следующее

sm = Group.create(id: 2, Name: 'System')
inf.groups << sm

Какой самый простой способ сделать это?

Ответы [ 2 ]

0 голосов
/ 07 февраля 2019

Я поместил мою попытку решения Джейка здесь в отдельном ответе, чтобы отделить его от оригинала и сделать комментарии и правки только для этого решения.Я НЕ собираюсь принимать это как ответ!Также я поместил весь сценарий здесь, чтобы люди могли проверить сразу.Решение использует только 2 таблицы вместо оригинальных 3, что хорошо, но еще не завершено, см. Мои замечания в конце кода.

require 'active_record'
require 'logger'

ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:database  => ":memory:"
)

ActiveRecord::Schema.define do
  create_table :users do |table|
    table.column :name, :string
    table.column :group_id, :integer
  end
  create_table :groups do |table|
    table.column :name, :string
    table.column :user_id, :integer
    table.column :parent_id, :integer
  end
end

class User < ActiveRecord::Base
  belongs_to :group
  has_many :groups
end

class Group < ActiveRecord::Base
  has_many :users
  has_many :sub_groups, class_name: "Group", foreign_key: :parent_id
  has_many :sub_group_users, through: :sub_groups, source: :users 
  belongs_to :parent, class_name: 'Group', foreign_key: :parent_id, optional: true
  # This is a scope to load the top level groups and eager-load their users, sub-groups, and the sub-groups' users too.
  scope :top_level, -> { where(parent_id: nil).includes :users, sub_groups: :users}
end

peter  = User.create(id: 1, name: 'Peter')
thomas = User.create(id: 2, name: 'Thomas')
erika  = User.create(id: 3, name: 'Erika')
inf    = Group.create(id: 1, name: 'Informatics')
devs   = Group.create(id: 2, name: 'Devs')
log    = Group.create(id: 3, name: 'Logistics')

# peter.groups << inf # doesn't work
devs.users << thomas
devs.users << peter
inf.users << erika

# expect inf.groups << devs to work
inf.sub_groups << devs

# doesn't work
p peter.groups #<ActiveRecord::Associations::CollectionProxy []>

# only gives users added straight to the group
p inf.users #<ActiveRecord::Associations::CollectionProxy [#<User id: 3, name: "Erika", group_id: 1>]>

# this should be added to inf.users
p inf.sub_group_users 
#<ActiveRecord::Associations::CollectionProxy [#<User id: 1, name: "Peter", group_id: 2>, #<User id: 2, name: "Thomas", group_id: 2>]>

# works
p Group.top_level
#<ActiveRecord::Relation [#<Group id: 1, name: "Informatics", user_id: nil, parent_id: nil>]>
0 голосов
/ 06 февраля 2019

Вы можете сделать это, используя отношение Self-Referential в вашей модели Group.Именно здесь записи в таблице могут указывать на другие записи в той же таблице.

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

Модель группы:

has_many :users

has_many :sub_groups, class_name: "Group", foreign_key: :parent_id
has_many :sub_group_users, through: :sub_groups, source: :users 
belongs_to :parent, class_name: 'Group', foreign_key: :parent_id, optional: true
# This is a scope to load the top level groups and eager-load their users, sub-groups, and the sub-groups' users too.
scope :top_level, -> { where(parent_id: nil).includes :users, sub_groups: :users}

Контроллер групп:

def show
  @group            = Group.find(params[:id])
  @category         = @group.parent
  @users            = @group.users
  @sub_group        = @group.sub_groups.first
  unless @sub_group.nil?
    @relatives      = @group.sub_group_users
  end
end

private

  def cat_params
    params.require(:group).permit(:name, :parent_id, :sub_group)
  end


  def main_group
    @group = Group.parent_id.nil?
  end

Inдобавьте в свою таблицу groups этот столбец: t.integer "parent_id"

добавьте в свою таблицу users этот столбец: t.integer "group_id"

Вам нужно будет добавить :group_id в свой user_params (в контроллере пользователя).

Модель пользователя: belongs_to :group

В ваших группах отображается вид:

<% if @category %>
  <% @users.each do |user| %>

  <% end %>
<% else %>
  <% @relatives&.each do |user| %>

  <% end %>
<% end %>
...