Question

Je suis en train de mettre en œuvre des relations multiples entre les enregistrements du même modèle par Autojointures (basé sur @ réponse de Shtééf). Je les modèles suivants

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

Avec cette configuration, je peux accéder avec succès pour chaque enregistrement les listes des subordonnés et des gestionnaires. Cependant, j'ai du mal à créer des relations de la façon suivante

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

Le problème est que ce type de relations ne pas ensemble, donc je dois écrire

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

Ai-je fait quelque chose de mal?

Était-ce utile?

La solution

Vous pouvez utiliser le rappel before_add et before_remove sur l'association has_many:

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 }

Il devrait fonctionner et vous rendre capable d'utiliser employe.managers.create
Vous pouvez utiliser instread build de create dans le rappel
vous pouvez également lire cette question sur cette solution

Autres conseils

Afin de créer un multiple many-to-many autojointure association, je recommande qu'il serait plus logique d'avoir plusieurs tables pour gérer la connexion. De cette façon, il est très clair du point de vue des données pour savoir exactement ce qui se passe, et il est également clair d'un point de vue logique. Donc, quelque chose le long de ces lignes:

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

De cette façon, il ne devient pas plus alambiquée que nécessaire du point de vue de codage, et vous pouvez y accéder en utilisant les collections standard et il sera mis en place de façon appropriée vos connexions pour vous sans que vous ayez à les gérer. Ainsi, ces deux collections devrait fonctionner ..

employee.managers
employee.subordinates

Et vous pourriez ne pas avoir à gérer les autres variables. Sens? Il ajoute une table, mais améliore la clarté.

Je refaire vos modèles comme suit:

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

Maintenant, vous pouvez faire ce qui suit:

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

Remarque: Il est généralement un signe pour un de quitter l'entreprise quand ils sont faits pour rapport à deux gestionnaires: -)

Compte tenu de la relation présentée

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

Bien que la solution fonctionne - je suis un peu confus en liant les associations avec « rel_type ». Dans ce cas - je dirais que le rel_type est redondant et la relation devrait être mis en correspondance comme suit:

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

Dans ce cas, la mise en correspondance d'association devrait être un peu plus simple.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top