2010-03-04 6 views
49

È possibile avere più relazioni has_many :through passanti l'una sull'altra in Rails? Ho ricevuto il suggerimento di farlo come soluzione per un'altra domanda che ho postato, ma non sono riuscito a farlo funzionare.Ruby-on-Rails: più has_many: attraverso possibile?

Gli amici sono un'associazione ciclica attraverso una tabella di join. L'obiettivo è quello di creare unper friends_comments, così posso prendere un User e fare qualcosa come user.friends_comments per ottenere tutti i commenti fatti dai suoi amici in una singola query.

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 

Questo sembra fantastico e ha senso, ma non funziona per me. Questo è l'errore che sto ricevendo in parte rilevante quando provo ad accedere friends_comments di un utente:
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)))

Quando ho appena entro in user.friends, che funziona, questa è la query esegue:
: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))

Quindi sembra che si stia completamente dimenticando dell'originale has_many tramite la relazione di amicizia, e quindi sta tentando in modo inappropriato di utilizzare la classe Utente come tabella di join.

Sto facendo qualcosa di sbagliato, o semplicemente non è possibile?

risposta

69

Edit:

Rails 3.1 supporta associazioni nidificate. E.g:

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

Non è necessaria la soluzione indicata di seguito. Fare riferimento a this screencast per ulteriori dettagli.

risposta originale

si passa un associazione has_many :through come fonte per un altro has_many :through associazione. Non penso che funzionerà.

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

Hai tre approcci per risolvere questo problema.

1) scrivere un'estensione associazione

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 

ora è possibile ottenere i commenti amici come segue:

user.friends.comments 

2) aggiungere un metodo alla classe User.

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

Ora potete ottenere gli amici commenti come segue:

user.friends_comments 

3) Se si desidera che questo sia ancora più efficiente allora:

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 

Ora è possibile ottenere i tuoi amici commenti come segue:

user.friends_comments 

Tutti i metodi memorizzano nella cache i risultati. Se si desidera ricaricare i risultati effettuare le seguenti operazioni:

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

O meglio ancora:

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

Sfortunatamente, lo scopo originale alla base di questo approccio era che potevo ottenere tutti i commenti di amici da SQL in una query. Vedi qui: http://stackoverflow.com/questions/2382642/ruby-on-rails-how-to-pull-out-most-recent-entries-from-a-limited-subset-of-a-dat Se scrivo una funzione, ciò produrrà un numero N + 1 di query. –

+1

No non lo farà. Saranno 2 domande. Prima query per ottenere gli amici e la seconda query per ottenere i commenti per ** tutti ** gli amici. Se hai già caricato gli amici sul modello utente, non incorrerai in alcun costo. Ho aggiornato la soluzione con altri due approcci. Guarda. –

+0

@williamjones Non hai problemi N + 1 in tutti e tre gli approcci. È possibile controllare il file di registro per garantire questo. Personalmente mi piace l'approccio 1 in quanto è piuttosto elegante, ad esempio 'user.friends.comments' è meglio di' user.friends_comments' –

8

V'è un plugin che risolve il problema, dare un'occhiata a this blog.

si installa il plugin con

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

Anche se questo non ha funzionato in passato, funziona benissimo in Rails 3.1 ora.