2010-03-04 19 views
49

¿Es posible tener múltiples relaciones has_many :through que se crucen en Rails? Recibí la sugerencia de hacerlo como una solución para otra pregunta que publiqué, pero no he podido lograr que funcione.Ruby-on-Rails: Multiple has_many: through possible?

Los amigos son un asociación cíclica a través de una tabla de unión. El objetivo es crear un has_many :through para friends_comments, entonces puedo tomar un User y hacer algo así como user.friends_comments para obtener todos los comentarios hechos por sus amigos en una sola consulta.

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 

Esto se ve muy bien, y tiene sentido, pero no funciona para mí. Este es el error que estoy recibiendo en su parte pertinente cuando intento acceder a la friends_comments de un usuario:
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)))

Cuando acaba de entrar en user.friends, que funciona, esta es la consulta se ejecuta:
: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users".id = "friendships".friend_id WHERE (("friendships".user_id = 1) AND ((status = 2)))

Parece que se está olvidando por completo del original has_many a través de la relación de amistad, y luego está tratando incorrectamente de utilizar la clase de usuario como una tabla de unión.

¿Estoy haciendo algo mal, o simplemente esto no es posible?

Respuesta

69

Editar:

Rails 3.1 es compatible con las asociaciones anidados. Por ejemplo:

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

No hay necesidad de que la solución dada a continuación. Consulte el screencast de this para obtener más detalles.

respuesta original

Usted está de paso una asociación has_many :through como fuente para otra asociación has_many :through . No creo que vaya a funcionar.

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

Tiene tres enfoques para resolver este problema.

1) escribir una extensión asociación

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 

Ahora usted puede obtener los comentarios de amigos de la siguiente manera:

user.friends.comments 

2) añadir un método para la clase User.

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

Ahora usted puede conseguir los amigos los comentarios de la siguiente manera:

user.friends_comments 

3) Si desea que esto sea aún más eficiente a continuación:

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 

Ahora puede obtener los amigos comentarios de la siguiente manera:

user.friends_comments 

Todos los métodos almacenan en caché los resultados. Si desea volver a cargar los resultados hacen lo siguiente:

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

O mejor aún:

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

Desafortunadamente, el propósito original de este enfoque era poder obtener todos los comentarios de los amigos de SQL en una consulta. Vea aquí: http://stackoverflow.com/questions/2382642/ruby-on-rails-how-to-pull-out-most-recent-entries-from-a-limited-subset-of-a-dat Si escribo una función, eso dará como resultado un número N + 1 de consultas. –

+1

No, no lo hará. Serán 2 consultas. Primera consulta para obtener los amigos y segunda consulta para obtener los comentarios de ** todos ** los amigos. Si ya ha cargado a los amigos en el modelo de usuario, entonces no incurre en ningún costo. He actualizado la solución con dos enfoques más. Echar un vistazo. –

+0

@williamjones No tiene un problema N + 1 en los tres enfoques. Puede verificar su archivo de registro para asegurarse de esto. Personalmente, me gusta el enfoque 1, ya que es bastante elegante, es decir, 'user.friends.comments' es mejor que' user.friends_comments' –

8

Hay un plugin que resuelve su problema, echar un vistazo a this blog.

de instalar el plugin con

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

Aunque esto no funcionó en el pasado, que funciona bien en Rails 3.1 ahora.

Cuestiones relacionadas