8

我試圖通過自連接(基於@Shtééf's answer)在同一模型的記錄之間實現多個關係。我有以下型號多對多與ActiveRecord中的多個自連接的關聯

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:...>] 

難道我做錯了什麼?

回答

8

您可以使用上的has_many協會before_addbefore_remove回調:

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回調
還你可以閱讀this question關於這個解決方案

3

爲了創建一個多對多的自連接關聯,我建議使用多個表來管理連接可能更有意義。從數據的角度來看,從數​​據的角度來看,從邏輯的角度來看,這是非常明確的。所以,這些方針的東西:

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 

而且你不必管理任何其他變量。合理?它增加了一個表格,但提高了清晰度。

+0

而我並不完全確定belongs_to是必要的,但我沒有時間設置t他在我的本地安裝上進行全面練習。你可以試試看,看看你會得到什麼樣的結果。 – 2011-06-21 14:08:07

+1

另外,如果你喜歡走這條路,你應該看看這個問題。它顯示了你正在做的更多的事情,雖然我仍然發現自己有點缺乏清晰度(fyi-問題在問題中得到了回答):http://stackoverflow.com/questions/6426383/rails-協會使用數據的關聯表 – 2011-06-21 15:45:44

2

我會如下重做你的模型:

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 

注:這通常是一個離開公司時,他們的標誌被報告給兩位經理:-)

+0

感謝您的回答。我試圖避免爲每種關係類型添加額外的表格,但它可能是這樣做的最佳方式。 – Andrei 2011-06-28 16:37:25

2

鑑於所提出的關係

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 

在這種情況下,關聯映射應該是一點點簡單。