ActiveRecord 中与多个自联接的多对多关联

发布于 2024-11-15 08:23:10 字数 1325 浏览 5 评论 0原文

我正在尝试通过自连接实现同一模型的记录之间的多种关系(基于 @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:...>]

我做错了什么吗?

I am trying to implement multiple relations between records of the same model via self-joins (based on @Shtééf's answer). I have the following models

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

With this setup I can successfully access the lists of subordinates and managers for each record. However, I have difficulties to create relations in the following way

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

The problem is that it does not set type of relations, so I have to write

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

Am I doing something wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

腹黑女流氓 2024-11-22 08:23:10

您可以在 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 而不是 create
您还可以阅读有关此解决方案的此问题

You can use before_add and before_remove callback on the has_many association :

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 }

This should works and make you able to use employe.managers.create
You may want to use build instread of create in the callback
Also you can read this question about this solution

睫毛上残留的泪 2024-11-22 08:23:10

为了创建多个多对多自联接关联,我建议使用多个表来管理连接可能更有意义。这样,从数据的角度来看,到底发生了什么就非常清楚,从逻辑的角度来看也很清楚。因此,沿着这些思路:

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

并且您不必管理任何其他变量。有道理吗?它添加了一个表格,但提高了清晰度。

In order to create a multiple many-to-many self-join association, I would recommend that it might make more sense to have multiple tables to manage the connection. That way it's very clear from a data standpoint as to exactly what is going on, and it's also clear from a logic standpoint. So something along these lines:

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

This way it doesn't get any more convoluted than necessary from a coding standpoint, and you can access it using the standard collections and it will appropriately set up your connections for you without you having to manage them. So, both of these collections should work..

employee.managers
employee.subordinates

And you could not have to manage any other variables. Make sense? It adds a table, but improves clarity.

痴者 2024-11-22 08:23:10

我将按如下方式重做您的模型:

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

注意:当一个人被迫向两位经理汇报时,这通常是一个人离开公司的标志:-)

I would redo your models as follows:

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

Now you can do the following:

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

Note: It is usually a sign for one to leave the company when they are made to report to two managers :-)

追风人 2024-11-22 08:23:10

鉴于所提出的关系,

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

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

Given the presented relation

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

Although the solution works - I'm a bit confused by tying the associations with "rel_type". In this case - I'd say the rel_type is redundant and the relation should be mapped as follows:

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

In such case, the association mapping should be a tad simpler.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文