Ассоциация «многие ко многим» с несколькими самостоятельными объединениями в ActiveRecord - PullRequest
8 голосов
/ 15 июня 2011

Я пытаюсь реализовать множественные отношения между записями одной и той же модели с помощью самостоятельных соединений (на основе ответа @ Shtééf ). У меня есть следующие модели

create_table :relations, force: true do |t|
  t.references :employee_a
  t.string     :rel_type
  t.references :employee_b
end

class Relation < ActiveRecord::Base
  belongs_to :employee_a, :class_name => 'Employee'
  belongs_to :employee_b, :class_name => 'Employee'
end

class Employee < ActiveRecord::Base
  has_many :relations, foreign_key: 'employee_a_id'
  has_many :reverse_relations, class_name: 'Relation', foreign_key: 'employee_b_id'

  has_many :subordinates, through: :relations, source: 'employee_b', conditions: {'relations.rel_type' => 'manager of'}
  has_many :managers, through: :reverse_relations, source: 'employee_a', conditions: {'relations.rel_type' => 'manager of'}
end

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

e = Employee.create
e.subordinates.create
e.subordinates #=> []
e.managers.create
e.managers #=> []

Проблема в том, что он не устанавливает тип отношений, поэтому я должен написать

e = Employee.create
s = Employee.create
e.relations.create employee_b: s, rel_type: 'manager of'
e.subordinates #=> [#<Employee id:...>]

Я что-то не так делаю?

Ответы [ 4 ]

8 голосов
/ 25 июня 2011

Вы можете использовать before_add и before_remove обратный вызов для ассоциации has_many:

class Employee < ActiveRecord::Base
  has_many :relations, foreign_key: 'employee_a_id'
  has_many :reverse_relations, class_name: 'Relation', foreign_key: 'employee_b_id'

  has_many :subordinates, 
           through: :relations, 
           source: 'employee_b', 
           conditions: {'relations.rel_type' => 'manager of'}
           :before_add => Proc.new { |employe,subordinate| employe.relations.create(employe_b: subordinate, rel_type: 'manager of') },
           :before_remove => Proc.new { |employe,subordinate| employe.relations.where(employe_b: subordinate, rel_type: 'manager of').first.destroy }

  has_many :managers,  
           through: :reverse_relations, 
           source: 'employee_a', 
           conditions: {'relations.rel_type' => 'manager of'}
           :before_add => Proc.new { |employe,manager| employe.reverse_relations.create(employe_a: manager, rel_type: 'manager of') },
           :before_remove => Proc.new { |employe,manager| employe.reverse_relations.where(employe_b: subordinate, rel_type: 'manager of').first.destroy }

Это должно работать и дать вам возможность использовать employe.managers.create
Возможно, вы захотите использовать build instread create в обратном вызове
Также вы можете прочитать этот вопрос об этом решении

3 голосов
/ 21 июня 2011

Чтобы создать множественную ассоциацию самостоятельного объединения «многие ко многим», я бы порекомендовал иметь больше смысла иметь несколько таблиц для управления соединением. Таким образом, это очень ясно с точки зрения данных относительно того, что именно происходит, и это также ясно с точки зрения логики. Итак, что-то вроде этого:

create_table :manage_relation do |t|
  t.references :employee_id
  t.references :manager_id
end
create_table :subordinate_relation do |t|
  t.references :employee_id
  t.references :subordinate_id
end

class Employee < ActiveRecord::Base

  has_many :subordinates, 
           :through => :subordinate_relation, 
           :class_name => "Employee", 
           :foreign_key => "subordinate_id"
  has_many :managers, 
           :through => :manage_relation, 
           :class_name => "Employee", 
           :foreign_key => "manager_id"

  belongs_to :employee, 
             :class_name => "Employee"
end

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

employee.managers
employee.subordinates

И вам не нужно было управлять никакими другими переменными. Есть смысл? Добавляет таблицу, но улучшает ясность.

2 голосов
/ 25 июня 2011

Учитывая представленное отношение

create_table :relations, force: true do |t|
  t.references :employee_a
  t.string     :rel_type
  t.references :employee_b
end


class Employee < ActiveRecord::Base

  has_many :subordinate_relations, :class_name => "Relation", :conditions => {:rel_type => 'manager of'}, :foreign_key => :employee_a
  has_many :subordinates, :through => :subordinate_relations, :source => :subordinate, :foreign_key => :employee_b

  has_many :manager_relations, :class_name => "Relation", :conditions => {:rel_type => 'manager of'}, :foreign_key => :employee_b
  has_many :managers, :through => :manager_relations, :source => :manager, :foreign_key => :employee_a

end


class Relation < ActiveRecord::Base

  belongs_to :manager, :class_name => "Employee", :foreign_key => :employee_a
  belongs_to :subordinate, :class_name => "Employee", :foreign_key => :employee_b

end


e = Employee.create
e.subordinates.create #Employee ...
e.subordinates #[<Employee ...]

e2 = Employee.create
e2.managers.create #Employee
e2.managers #[<Employee ...]

Хотя решение работает - я немного запутался, связав ассоциации с "rel_type".В этом случае - я бы сказал, что rel_type является избыточным, и отношение должно отображаться следующим образом:

create_table :relations do |t|
    t.reference :manager
    t.reference :subordinate
end

В этом случае отображение ассоциации должно быть немного проще.

2 голосов
/ 22 июня 2011

Я бы повторил ваши модели следующим образом:

class ManagerRelation < ActiveRecord::Base
  belongs_to :manager, :class_name => 'Employee'
  belongs_to :subordinate, :class_name => 'Employee'
end

class Employee < ActiveRecord::Base
  has_many :manager_relations,     :class_name => "ManagerRelation",
               :foreign_key => :subordinate_id
  has_many :subordinate_relations, :class_name => "ManagerRelation", 
               :foreign_key => :manager_id

  has_many :managers,     :source => :manager,     
               :through => :manager_relations

  has_many :subordinates, :source => :subordinate, 
               :through => :subordinate_relations
end

Теперь вы можете сделать следующее:

employee.managers
employee.subordinates    
employee.managers << employee2    
employee.subordinates << employee3

Примечание: Обычно это знак дляодин, чтобы покинуть компанию, когда они сделаны, чтобы сообщить двум менеджерам: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...