2011-12-28 15 views
7

artistas tienen muchas actividades (básicamente una memoria caché de las interacciones entre los usuarios):¿hay alguna forma de cargar asociaciones de asociaciones polimórficas ansiosas?

class Activity < ActiveRecord::Base 

    belongs_to :receiver, :class_name => 'Artist', :foreign_key => :receiver_id #owns the stuff done "TO" him 
    belongs_to :link, :polymorphic => true 
    belongs_to :creator, :class_name => 'Artist', :foreign_key => :creator_id #person who initiated the activity 

end 

Por ejemplo:

Activity.create(:receiver_id => author_id, :creator_id => artist_id, :link_id => id, :link_type => 'ArtRating') 

Quiero crear una página de Tráfico de actividad para cada artista, que consiste en una lista de diferentes tipos de eventos, ArtRatings (gustos, disgustos), como favoritos, etc. Siguiendo

El controlador es el siguiente:

class ActivityStreamController < ApplicationController 
    def index 
    @activities = @artist.activities.includes([:link,:creator,:receiver]).order("id DESC").limit(30) 
    end 
end 

La llamada db correctamente carga con impaciencia los objetos de vínculos polimórficas:

SELECT "activities".* FROM "activities" WHERE (("activities"."receiver_id" = 6 OR "activities"."creator_id" = 6)) ORDER BY id DESC LIMIT 30 
    ArtRating Load (0.5ms) SELECT "art_ratings".* FROM "art_ratings" WHERE "art_ratings"."id" IN (137, 136, 133, 130, 126, 125, 114, 104, 103, 95, 85, 80, 73, 64) 
    SELECT "follows".* FROM "follows" WHERE "follows"."id" IN (14, 10) 
    SELECT "favorites".* FROM "favorites" WHERE "favorites"."id" IN (25, 16, 14) 

Pero cuando se muestre cada ArtRating, también debe hacer referencia al título de la entrada, que pertenece a un poste. En la vista, si lo hago:

activity.link.post 

Hace una llamada de base de datos separada para cada publicación de art_rating. ¿Hay alguna manera de cargar ansiosamente la publicación también?

ACTUALIZACIÓN DE LA PREGUNTA:

Si no hay manera de alcanzar la carga con ganas de mensajes usando 'incluye' sintaxis, es que hay una manera de hacerlo manualmente la carga ansiosa consultar a mí mismo y lo inyecta en el @ actividades objeto?

veo en el registro DB:

SELECT "art_ratings".* FROM "art_ratings" WHERE "art_ratings"."id" IN (137, 136, 133, 130, 126, 125, 114, 104, 103, 95, 85, 80, 73, 64) 

¿Hay alguna manera de que pueda acceder a esta lista de identificadores de los @activities objeto? De ser así, podría hacer 2 consultas adicionales, 1 para obtener las art_ratings.post_id (s) en esa lista, y otra para SELECCIONAR todas las publicaciones EN esa lista de post_id. Luego, de alguna forma, inyecte los resultados de "publicación" en @activities para que esté disponible como activity.link.post cuando repite la colección. ¿Posible?

+0

¿hay algún progreso aquí? – bogardon

+0

Por lo que entiendo, tiene un conjunto de modelos que se supone que están vinculados a actividades como enlaces y creo que todos comparten al menos una asociación, que es 'post'. ¿Hay quizás suficiente terreno común en estos modelos de enlaces para usar [Single Table Herencia] (http://apidock.com/rails/ActiveRecord/Base) y poner todo en una tabla? – CMW

Respuesta

0

Prueba esto:

@artist.activities.includes([{:link => :post},:creator,:receiver])... 

See the Rails docs for more.

+0

normalmente eso funcionaría, pero los documentos no dicen nada acerca de la carga ansiosa a través de la asociación polimórfica. Error al obtener: "Asociación llamada 'publicación' no fue encontrada, ¿quizás lo deletreaste mal?" – Homan

+0

He probado esto y funciona bien (3.2.14). ¿Estás seguro de que todas tus asociaciones de "enlaces" tienen la asociación de publicaciones? – aromero

0

Si consigo este derecho, no sólo se desea cargar la asociación de una asociación polimórfica , pero de una asociación polimórfica asociación polimórfica.

Esto básicamente significa unir una tabla definida con otra tabla definida a través de un grupo de tablas indefinidas que provienen de algunos campos en la base de datos.

Dado que tanto la actividad como la publicación se unen mediante un objeto polimórfico, ambas tienen una columna something_id y something_type.

ahora pienso registro activo no le permite hacer esto fuera de la caja, pero básicamente desea algo como:

class Activity 
    has_one :post, :primary_key => [:link_id, :link_type], :foreign_key => [:postable_id, :postable_type] 
end 

(suponiendo que su asociación polimórfica en Post es belongs_to :postable)

Eso haría luego le damos una consulta de tipo de asociación directa entre Post y Activity que es una versión muy extraña de habtm con una 'tabla de unión polimórfica' (me acaba de hacer ese término). Como ambos comparten el mismo objeto asociado polimórficamente, se pueden conectar. Algo así como una cosa de amigos de mis amigos.

CAVEAT: Como he dicho, hasta donde yo sé, AR no te permite hacer esto de la caja (aunque sería increíble) pero hay algunas gemas que te dan un compuesto exterior y/o llaves principales. Tal vez puedas encontrar uno que te ayude a resolver tu problema de esta manera.

0

Dado su actualización a la pregunta (que esto no es factible el uso de include AR), que podría intentar lo siguiente para obtener los mensajes asociados con una sola consulta adicional:

  • buscar el Actividades, a continuación, construir una matriz de su post_ids vinculado.

    @activities = @artist.activities.includes([:link,:creator,:receiver]) 
    activity_post_ids = @activities.map{|a| a.link.post_id}.compact 
    
  • entonces cargar los mensajes en una consulta y almacenarlas en un Hash, indexada por su id

    activity_posts = Hash[Post.where(id: activity_post_ids).map{|p| [p.id, p]}] 
    #=> {1 => #<Post id:1>, 3 => #<Post id:3>, ... } 
    
  • finalmente bucle sobre @activities y establece el atributo post de cada enlace asociado

    @activities.each{|a| a.link.post = activity_posts[a.link.post_id]} 
    
4

TL; DR mi solución hace artist.created_activities.includes(:link) carga todo lo ansiosos que quieren

Aquí está mi primera intento de que: https://github.com/JohnAmican/music

Unas pocas notas:

  • estoy confiando en default_scope , entonces esto no es óptimo
  • Parece que está usando STI. Mi solución no. Eso significa que no puede simplemente llamar al activities en un artista; debe hacer referencia a created_activities o received_activities. Puede haber una forma de evitar esto. Actualizaré si encuentro algo.
  • Cambié algunos nombres porque de otro modo era confuso para mí.

Si usted entra en la consola y hacer created_activities.includes(:link), la materia apropiada obtiene ansioso cargado:

irb(main):018:0> artist.created_activities.includes(:link) 
    Activity Load (0.2ms) SELECT "activities".* FROM "activities" WHERE "activities"."creator_id" = ? [["creator_id", 1]] 
    Rating Load (0.3ms) SELECT "ratings".* FROM "ratings" WHERE "ratings"."id" IN (1) 
    RatingExplanation Load (0.3ms) SELECT "rating_explanations".* FROM "rating_explanations" WHERE "rating_explanations"."rating_id" IN (1) 
    Following Load (0.3ms) SELECT "followings".* FROM "followings" WHERE "followings"."id" IN (1) 
    Favorite Load (0.2ms) SELECT "favorites".* FROM "favorites" WHERE "favorites"."id" IN (1) 
=> #<ActiveRecord::Relation [#<Activity id: 1, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Rating", created_at: "2013-10-31 02:36:27", updated_at: "2013-10-31 02:36:27">, #<Activity id: 2, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Following", created_at: "2013-10-31 02:36:41", updated_at: "2013-10-31 02:36:41">, #<Activity id: 3, receiver_id: 2, creator_id: 1, link_id: 1, link_type: "Favorite", created_at: "2013-10-31 02:37:04", updated_at: "2013-10-31 02:37:04">]> 

Al menos, esto demuestra que los carriles tiene la capacidad de hacer esto. Evitar default_scope parece ser un problema para decirle a Rails lo que quiere hacer, en lugar de una limitación técnica.

UPDATE:

Resulta que cuando se pasa de un bloque alcance a una asociación y llamar a que la asociación, self dentro de ese bloque se refiere a la relación. Por lo tanto, se puede reflexionar sobre ello y actuar en consecuencia:

class Activity < ActiveRecord::Base 
    belongs_to :creator, class_name: 'Artist', foreign_key: :creator_id, inverse_of: :created_activities 
    belongs_to :receiver, class_name: 'Artist', foreign_key: :receiver_id, inverse_of: :received_activities 
    belongs_to :link, -> { self.reflections[:activity].active_record == Rating ? includes(:rating_explanation) : scoped }, polymorphic: true 
end 

He actualizado mi código para reflejar (jaja) esto.

Esto se puede limpiar. Por ejemplo, tal vez no siempre desee cargar ansiosamente rating_explanations al acceder a los enlaces de actividad. Hay varias formas de resolver eso. Podría publicar uno si lo desea.

Pero, creo que lo más importante que esto muestra es que dentro del bloque de alcance de la asociación, tiene acceso al ActiveRecord::Relation que se está construyendo. Esto te permitirá hacer cosas condicionalmente.

0

Este sencillo ActiveRecord debería funcionar:

@artist.activities.includes([:link => :post,:creator,:receiver]).order("id DESC").limit(30) 

Si no es así, si usted está recibiendo un error como "Association named 'post' was not found; perhaps you misspelled it?", entonces usted tiene un problema de modelo, ya que al menos uno de sus asociaciones link no tiene la post asociación.

Las asociaciones polimórficas están destinadas a ser utilizadas con una interfaz común. Si solicita el post para cualquiera de las asociaciones polimórficas, TODAS sus asociaciones deberían implementar también esa asociación (el post).

Compruebe que todos los modelos utilizados en la asociación polimórfica implementen la asociación de publicaciones.

Cuestiones relacionadas