2010-08-06 5 views
4

Il semble y avoir un certain nombre de façons de gérer une association de clé étrangère multiple. Chaque fois que j'ai approché cela a ses inconvénients, et comme je suis nouveau à Rails, je suis convaincu que d'autres ont rencontré un scénario similaire et je travaille probablement sur quelque chose de résolu depuis longtemps.Rails - Association de clés d'index multiples

Ma question est:

Quelle serait un moyen efficace de gérer un index multiple association clé, tout en conservant tous les autres Rails modificateurs de sql (tels que: include etc.)?

Mon scénario est le suivant:

J'ai une association de table comme suit (simplifié), qui est utilisé pour connecter les gens à d'autres personnes via des liens:

People 
+----+-----------+ 
| id | name  | 
+----+-----------+ 
| 1 | Joe  | 
+----+-----------+ 
| 2 | Sally  | 
+----+-----------+ 
| 3 | Bob  | 
+----+-----------+     

Links 
+----+-----------+---------+ 
| id | origin_id | rcvd_id | 
+----+-----------+---------+ 
| 1 | 2   | 1  | 
+----+-----------+---------+ 
| 2 | 1   | 3  | 
+----+-----------+---------+       
| 3 | 3   | 2  | 
+----+-----------+---------+       

De la ligne 1 des liens ci-dessus table, on peut voir qu'une Personne (Sally = 2) est liée à une autre Personne (Joe = 1).

Il m'est facile de trouver tous les liens de personnes si ma clé étrangère était "origin_id". Mais cela ne montrerait que les gens qui ont créé un lien. Dans mon scénario, j'ai besoin de voir tous les liens, qu'ils aient été créés ou reçus par une personne. Si, par exemple, je devais demander tous les liens de Sally (Sally = 2), le résultat que je voudrais serait:

Links 
+----+-----------+---------+ 
| id | origin_id | rcvd_id | 
+----+-----------+---------+ 
| 1 | 2   | 1  | 
+----+-----------+---------+       
| 3 | 3   | 2  | 
+----+-----------+---------+       

D'où j'ai 2 clés d'index, les deux « origin_id » et « rcvd_id ».

Une façon cela pourrait être résolu est une méthode:

class Person < ActiveRecord::Base 
    has_many :link_origins, :class_name => "Link", :foreign_key => :origin_id, :dependent => :destroy 
    has_many :link_rcvds, :class_name => "Link", :foreign_key => :rcvd_id, :dependent => :destroy 

def links 
    origin_person + rcvd_person 
end 

Cependant, ce n'est pas efficace. Par exemple, cela nécessite que toute la collection soit collectée à partir de la base de données et ensuite seulement la méthode paginate fonctionne (j'utilise la gemme will_paginate), ce qui détruit le point car paginate devrait accélérer le processus en limitant le nombre d'enregistrements appelés. Ne pas limiter les enregistrements après que la collection entière est déjà faite.

Aussi, ce qui précède ne me permettra pas d'appeler par exemple, Joe.links (: first) .origin_id.name. Pas exactement ce code mais signifiant que je ne pouvais pas appeler les détails de la personne sur l'identifiant origin_id d'un lien sélectionné, car la méthode links ne sait pas que origin_id est lié à la table People.

Jusqu'à présent, la solution la plus pratique semble être: finder_sql.

class Person < ActiveRecord::Base 
    has_many :links, :finder_sql => 'SELECT * FROM links WHERE (links.origin_id = #{id} or links.rcvd_id = #{id})' 

Cela donne tous les liens où le person_id correspond soit au Links.origin_id ou Links.rcvd_id. L'inconvénient de cette option est que l'utilisation de: finder_sql exclut tous les autres modificateurs sql puisque Rails ne sait pas comment analyser et modifier le SQL que vous fournissez. Par exemple je ne serais pas capable d'utiliser l'option: include avec le: finder_sql. Donc, en ce moment, j'utilise la solution: finder_sql, solution. Mais il semble qu'il pourrait y avoir un moyen d'obtenir que cette association soit faite de telle manière que je n'ai pas besoin d'un: finder_sql. Par exemple, existe-t-il un moyen d'écrire une chaîne sql personnalisée tout en conservant les modificateurs SQL Rails fournis par Active Record.

Des idées sur ce sujet?

Répondre

3

J'ai trouvé la solution à cela, mais il s'est avéré que je posais probablement la mauvaise question. Je n'ai pas trouvé loin d'avoir plusieurs clefs d'index comme je l'ai demandé sans implémenter un sql personnalisé qui casse différents aides de rails.

Je suppose que ma question est toujours là, mais comment j'ai résolu cela, c'était de regarder le problème différemment. Je viens de créer les associations comme ils sont:

belongs_to :rcvd_person, :class_name => 'Person', :foreign_key => :rcvd_id 
belongs_to :origin_person, :class_name => 'Person', :foreign_key => :origin_id 

Et une instruction SQL personnalisée:

class Person... 
    has_many :links, :finder_sql => 'SELECT * FROM links WHERE origin_id = #{id} OR rcvd_id = #{id}' 
end 

Alors j'ai pu manipuler les dossiers que je voulais à mon avis. Dans le cas où quelqu'un d'autre est en train de faire quelque chose de semblable, je l'ai fait:

<% person.links.each do |link| %> 

    <% if link.origin_id == person.id %> 
    <%= link.rcvd_person.given_name %> 
    <% else %> 
    <%= link.origin_person.given_name %> 
    <% end %> 

<% end %> 
1

Je ne suis pas sûr que vous pouvez vraiment soutenir une association avec plusieurs clés dans la même table parce que Rails ne saura pas quelle touche pour définir si vous essayez pour créer une relation.

Cependant, si vous voulez juste person.links, Rails 3 fournit une manière qui est mieux que: finder_sql

class Link 
    def self.by_person(person) 
    where("origin_id => :person_id OR rcvd_id => :person_id", :person_id => person.id 
    end 
end 

class Person < ActiveRecord::Base 
    # You can include the has_many relationships if you want, or not 

    def links 
    Link.by_person(self) 
    end 
end 

Cela vous permet de faire des choses comme @ person.links.limit (3) (qui actuellement semble être cassé en utilisant: finder_sql)

+0

Merci Lelon. Je n'ai pas encore sauté sur Rails3 (je sais, embarrassant), mais j'y reviendrai bientôt. – Oscar

Questions connexes