8

我正在尝试通过自连接(基于@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

4 回答 4

8

您可以在 has_many 关联上使用before_add和回调:before_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
您可能希望在回调中使用buildinstread of 您也可以阅读有关此解决方案的这个问题create

于 2011-06-25T12:28:36.560 回答
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

而且您不必管理任何其他变量。说得通?它添加了一个表格,但提高了清晰度。

于 2011-06-21T14:07:09.250 回答
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

注意:当他们被要求向两位经理报告时,通常是一个人离开公司的标志:-)

于 2011-06-22T05:02:33.150 回答
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

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

于 2011-06-25T06:57:34.100 回答