Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Many-to-many association with multiple self-joins in ActiveRecord

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?

like image 839
Andrei Avatar asked Jun 14 '11 22:06

Andrei


1 Answers

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

like image 145
Adrien Coquio Avatar answered Oct 03 '22 04:10

Adrien Coquio