2010-03-04 15 views
49

Ist es möglich, mehrere has_many :through Beziehungen zu haben, die sich in Rails durchsetzen? Ich habe den Vorschlag erhalten, dies als Lösung für eine andere Frage zu tun, die ich veröffentlicht habe, aber ich konnte sie nicht zur Arbeit bringen.Ruby-on-Rails: Mehrere has_many: durch möglich?

Freunde sind eine zyklische Zuordnung durch eine Join-Tabelle. Das Ziel ist, ein has_many :through für friends_comments zu erstellen, also kann ich ein User nehmen und etwas wie user.friends_comments tun, um alle Anmerkungen zu erhalten, die von seinen Freunden in einer einzelnen Frage gemacht werden.

class User 
    has_many :friendships 
    has_many :friends, 
      :through => :friendships, 
      :conditions => "status = #{Friendship::FULL}" 
    has_many :comments 
    has_many :friends_comments, :through => :friends, :source => :comments 
end 

class Friendship < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :friend, :class_name => "User", :foreign_key => "friend_id" 
end 

Das sieht gut aus, und macht Sinn, aber funktioniert nicht für mich. Dies ist der Fehler ich in relevanten Teil bekommen, wenn ich versuche, eine friends_comments des Benutzers zuzugreifen:
ERROR: column users.user_id does not exist
: SELECT "comments".* FROM "comments" INNER JOIN "users" ON "comments".user_id = "users".id WHERE (("users".user_id = 1) AND ((status = 2)))

Wenn ich nur user.friends eingeben, die funktioniert, ist dies die Abfrage führt sie:
: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))

Es scheint also so, als würde man die ursprüngliche has_many durch Freundschaftsbeziehung völlig vergessen, und versucht dann unangemessen, die User-Klasse als Join-Tabelle zu verwenden.

Mache ich etwas falsch, oder ist das einfach nicht möglich?

Antwort

69

Edit:

Rails 3.1 unterstützt verschachtelte Assoziationen. Zum Beispiel:

has_many :tasks 
has_many :assigments, :through => :tasks 
has_many :users, :through => :assignments 

Die unten angegebene Lösung ist nicht erforderlich. Weitere Informationen finden Sie unter this Screencast.

Original-Antwort

Sie passieren eine has_many :through Vereinigung als Quelle für einen anderen has_many :through Verein. Ich denke nicht, dass es funktionieren wird.

has_many :friends, 
      :through => :friendships, 
      :conditions => "status = #{Friendship::FULL}" 
    has_many :friends_comments, :through => :friends, :source => :comments 

Sie haben drei Ansätze zur Lösung dieses Problems.

1) eine Vereinigung Erweiterung

has_many :friends, 
      :through => :friendships, 
      :conditions => "status = #{Friendship::FULL}" do 
    def comments(reload=false) 
     @comments = nil if reload 
     @comments ||=Comment.find_all_by_user_id(map(&:id)) 
    end 
end 

Jetzt können Sie die Freunde Kommentare erhalten wie folgt:

user.friends.comments 

2) Fügen Sie eine Methode zum User Klasse.

def friends_comments(reload=false) 
    @friends_comments = nil if reload 
    @friends_comments ||=Comment.find_all_by_user_id(self.friend_ids) 
    end 

Jetzt können Sie die Freunde Kommentare erhalten wie folgt:

user.friends_comments 

3) Wenn Sie dies noch effizienter sein wollen, dann:

def friends_comments(reload=false) 
    @friends_comments = nil if reload 
    @friends_comments ||=Comment.all( 
      :joins => "JOIN (SELECT friend_id AS user_id 
           FROM friendships 
           WHERE user_id = #{self.id} 
         ) AS friends ON comments.user_id = friends.user_id") 
    end 

Jetzt können Sie die Freunde bekommen Kommentare wie folgt:

user.friends_comments 

Alle Methoden speichern die Ergebnisse zwischen. Wenn Sie die Ergebnisse gehen Sie wie folgt neu geladen werden soll:

user.friends_comments(true) 
user.friends.comments(true) 

oder besser noch:

user.friends_comments(:reload) 
user.friends.comments(:reload) 
+1

Leider hinter diesem Ansatz war der ursprüngliche Zweck, so dass ich alle Freunde Kommentare von SQL in einer Abfrage zu bekommen. Siehe hier: http://stackoverflow.com/questions/2382642/ruby-on-rails-how-to-pull-out-most-recent-entries-from-a-limited-subset-of-a-dat Wenn ich eine Funktion schreibe, wird das zu einer N + 1 Anzahl von Abfragen führen. –

+1

Nein, wird es nicht. Es werden 2 Anfragen sein. Erste Abfrage um die Freunde zu bekommen und zweite Abfrage um die Kommentare für ** alle ** Freunde zu bekommen. Wenn Sie die Freunde bereits in das Benutzermodell geladen haben, entstehen Ihnen keine Kosten. Ich habe die Lösung um zwei weitere Ansätze erweitert. Schau mal. –

+0

@williamjones Sie haben kein N + 1 Problem in allen drei Ansätzen. Sie können Ihre Protokolldatei überprüfen, um dies sicherzustellen. Ich persönlich mag Ansatz 1, da es recht elegant ist, das heißt 'user.friends.comments' ist besser als' user.friends_comments' –

8

Es ist ein Plugin, das Ihr Problem löst, einen Blick auf this blog nehmen.

Sie installieren das Plugin mit

script/plugin install git://github.com/ianwhite/nested_has_many_through.git 
4

Obwohl dies nicht in der Vergangenheit nicht funktioniert, es funktioniert gut in Rails jetzt 3.1.