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 

在这种情况下,关联映射应该是一点点简单。