2011-12-28 20 views
7

artisti hanno molte attività (in pratica una cache di interazioni tra gli utenti):c'è un modo per caricare le associazioni delle associazioni polimorfiche?

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 

Ad esempio:

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

Voglio creare una pagina Attività per ogni artista, costituito da un elenco di diversi tipi di eventi, ArtRatings (piace, non piace), aggiunta ai preferiti, seguito ecc

Il controller è simile al seguente:

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

La chiamata db correttamente carica con entusiasmo gli oggetti collegamento polimorfici:

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) 

Ma quando ho visualizzare ogni ArtRating, ho anche bisogno di fare riferimento il titolo del post, che appartiene a un post. Dal punto di vista, se faccio:

activity.link.post 

Lo fa una chiamata DB separato per il post di ogni art_rating. C'è un modo per caricare anche il post con impazienza?

AGGIORNAMENTO SULLA QUESTIONE:

Se non v'è alcun modo per ottenere eager loading dei messaggi usando 'include' la sintassi, c'è un modo per farlo manualmente l'eager loading interrogare me stesso e iniettarlo nel @ attività oggetto?

vedo nel 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) 

C'è un modo per accedere a questa lista di ID dalle @activities oggetto? In tal caso, potrei fare altre 2 query, 1 per ottenere art_ratings.post_id (s) in quell'elenco e un altro per SELEZIONARE tutti i post in quella lista di post_id. Quindi in qualche modo iniettare i risultati "post" in @activities in modo che sia disponibile come activity.link.post quando si itera la collezione. Possibile?

+0

qualsiasi progresso qui? – bogardon

+0

Da quello che ho capito, hai una serie di modelli che dovrebbero essere collegati alle attività come link e io racconto che condividono almeno un'associazione, che è 'post'. C'è forse abbastanza terreno comune in questi modelli di link per usare [Single Table Inheritance] (http://apidock.com/rails/ActiveRecord/Base) e mettere tutto in una tabella? – CMW

risposta

0

Prova questo:

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

See the Rails docs for more.

+0

normalmente funzionerebbe, ma i documenti non dicono nulla sul caricamento ansioso attraverso l'associazione polimorfica. Ottieni l'errore: "Associazione denominata 'post' non è stata trovata, forse l'hai sbagliata?" – Homan

+0

L'ho provato e funziona correttamente (3.2.14). Sei sicuro che tutte le tue associazioni di "link" abbiano l'associazione post? – aromero

0

Se ho questo diritto, non solo si desidera caricare l'associazione di un polimorfica dell'associazione , ma l'associazione polimorfica un polimorfica dell'associazione .

Ciò significa fondamentalmente unire una tabella definita con un'altra tabella definita tramite un gruppo di tabelle non definite provenienti da alcuni campi nel database.

Poiché sia ​​l'attività che il post sono uniti tramite un oggetto polimorfico, entrambi hanno una colonna something_id e something_type.

ora penso record attivo non consente di fare questo, fuori dalla scatola, ma fondamentalmente si desidera qualcosa di simile:

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

(supponendo vostra associazione polimorfica su Post è belongs_to :postable)

che sarebbe quindi fornisci una sorta di associazione diretta tra Post e Activity che è una presa molto strana su habtm con una "tabella di unione polimorfica" (ho appena fatto questo termine). Poiché entrambi condividono lo stesso oggetto associato polimorfo, possono essere collegati. Un po 'come una cosa degli amici dei miei amici.

CAVEAT: Come ho detto, per quanto ne so, AR non consente di fare questo fuori dalla scatola (anche se sarebbe impressionante), ma ci sono alcune gemme che ti danno composito estera e/o chiavi primarie. Forse puoi trovarne uno per aiutarti a risolvere il tuo problema in questo modo.

0

Dato l'aggiornamento alla domanda (che questo non è fattibile utilizzando del include AR), si potrebbe provare quanto segue per ottenere i messaggi associati a una singola query in più:

  • recuperare i Attività, quindi costruire una matrice dei loro link post_ids.

    @activities = @artist.activities.includes([:link,:creator,:receiver]) 
    activity_post_ids = @activities.map{|a| a.link.post_id}.compact 
    
  • poi caricare i messaggi in una query e li memorizza in una Hash, indicizzati da loro id

    activity_posts = Hash[Post.where(id: activity_post_ids).map{|p| [p.id, p]}] 
    #=> {1 => #<Post id:1>, 3 => #<Post id:3>, ... } 
    
  • infine ciclo su @activities e impostare l'attributo post di ogni collegamento associato

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

TL; DR mia soluzione rende artist.created_activities.includes(:link) desiderosi di carico tutto ciò che si vuole

Ecco il mio prima tentativo di esso: https://github.com/JohnAmican/music

Alcune note:

  • sto contando su default_scope , quindi questo non è ottimale.
  • Sembra che tu stia utilizzando STI. La mia soluzione no. Ciò significa che non puoi semplicemente chiamare activities su un artista; devi fare riferimento a created_activities o received_activities. Potrebbe esserci un modo per aggirare questo. Aggiornerò se trovo qualcosa.
  • Ho cambiato alcuni nomi perché mi confondeva il contrario.

Se vai in console e fare created_activities.includes(:link), la roba appropriata ottiene ansioso-caricato:

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">]> 

Per lo meno, questo dimostra che Rails ha la capacità di farlo. Evidenziare default_scope sembra un problema nel dire a Rails cosa vuoi fare piuttosto che una limitazione tecnica.

UPDATE:

scopre che quando si passa un blocco portata un'associazione e chiamare detta associazione, self all'interno di tale blocco si riferisce alla relazione. Quindi, puoi riflettere su di esso e agire in modo appropriato:

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 

Ho aggiornato il mio codice per riflettere (haha) questo.

Questo può essere pulito. Ad esempio, forse non si desidera sempre caricare sollecitamente rating_explanations quando si accede ai collegamenti di attività. Ci sono diversi modi per risolverlo. Potrei postarne uno se lo desideri.

Ma, penso che la cosa più importante che questo dimostra sia che all'interno del blocco scope dell'associazione, si ha accesso allo ActiveRecord::Relation in fase di costruzione. Questo ti permetterà di fare le cose condizionatamente ad esso.

0

Questo semplice ActiveRecord dovrebbe funzionare:

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

In caso contrario, se hai trovato un errore come "Association named 'post' was not found; perhaps you misspelled it?", allora avete un problema modello, perché almeno uno dei tuoi link associazioni non si ha il post associazione.

Le associazioni polimorfiche devono essere utilizzate con un'interfaccia comune. Se si richiede lo post per qualsiasi associazione polimorfica, allora TUTTE le associazioni dovrebbero implementare anche quell'associazione (quella post).

Verificare che tutti i modelli utilizzati nell'associazione polimorfica implementino l'associazione post.

Problemi correlati