2014-05-20 9 views
12

Ho un modello Luogo e un modello Event. Luoghi possono eventi che hanno luogo in una data specifica.Il precarico ha_molte associazioni con condizioni dinamiche

Come posso impostare le mie associazioni e i miei finder per caricare tutti i posti incluso (caricamento ansioso) i loro eventi in una data specifica senza N + 1 problema di query?

Quello che ho provato:

class Place 
    has_many :events 
end 

Place.all.preload(:events).where("events.start_date > '#{time_in_the_future}'") 
#ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "events". 

Place.all.includes(:events).where("events.start_date > '#{time_in_the_future}'").references(:event) 
# only loads places that have an event at the specific date and not all places including their events (if there are any events). 

sono venuto con successo con un'associazione che fa quello che voglio, ma non è dinamica (non accetta parametri)

class Place 
    has_many :events, -> {where("events.start_date > '#{Time.now}'")} 
end 

Place.all.preload(:events) 
# perfect: executes two queries: One to get all 'places' and one to get all 'events' that belong to the places and merges the 'events' into the 'place' objects. 
# But I can't pass time as a parameter, so time is always Time.now (as specified in the has_many association). 
# Place.all.preload(:events).where(xyz) gives wrong results like the examples above. 

Il problema per me è che non riesco a trovare un modo di precaricare/caricare carico con condizioni dinamiche. Perché precarico e include si aspettano il nome dell'associazione come parametro e non possono essere rifiniti con parametri. Almeno non ho trovato modo di farlo.

+1

Questo è un bel articolo che spiega eager loading in Rails: http://blog.arkency.com/2013/12/rails4-preloading/ – MurifoX

+0

Grazie, ma già letto questo articolo un paio di volte, ma non è stato possibile trovare informazioni sulle condizioni dinamiche durante il precaricamento. – nvano

+0

Impossibile trovare un modo per passare argomenti a ActiveRecord :: Associations :: Preloader, anche. Sovrascrittura o sottoclasse sembra essere un approccio sbagliato. – nvano

risposta

3

Questa sembra essere l'unica soluzione che funziona:

# 1st query: load places 
places = Place.all.to_a 

# 2nd query: load events for given places, matching the date condition 
events = Event.where(place: places.map(&:id)).where("start_date > '#{time_in_the_future}'") 
events_by_place_id = events.group_by(&:place_id) 

# 3: manually set the association 
places.each do |place| 
    events = events_by_place_id[place.id] || [] 

    association = place.association(:events) 
    association.loaded! 
    association.target.concat(events) 
    events.each { |event| association.set_inverse_instance(event) } 
end 

è un po 'hacky ma è abbastanza facile da adattare a qualsiasi situazione in cui si potrebbe desiderare di caricare un'associazione utilizzando una query separata e poi attaccarlo a un oggetto esistente.

Tutto il merito va a https://mrbrdo.wordpress.com/2013/09/25/manually-preloading-associations-in-rails-using-custom-scopessql/

+1

Semplicemente fantastico ... finalmente qualcosa che funziona. Una cosa - 'to_a' nel primo passaggio ti impedirà di aggiungere ulteriori concatenazioni di query, ecc. Ma in realtà non è necessario convertire i posti in una matrice. –

+0

La prima query è solo un esempio, non è necessario convertire esplicitamente in array ('map' esegue l'esecuzione, comunque). Puoi anche aggiungere ulteriori filtri, se lo desideri. Hai un'idea. –

0

Come ho capito, vuoi recuperare tutti i luoghi che hanno almeno un evento che soddisfa alcune condizioni, ma i luoghi dovrebbero essere recuperati con tutti gli eventi, anche quelli che non soddisfano le condizioni. Non è possibile capire che questo è fuori con una semplice query, ma se si utilizzerà suquery allora il problema sarà fatto. Ecco la soluzione:

Place.includes(:events).where(id: Place.joins(:events).where("events.start_date > '#{time_in_the_future}'")).references(:events) 

C'è una query complessa che verrà costruita, ma fa le cose per bene.

0

In alcuni casi ho menzionato che includes non seleziona correttamente il metodo di caricamento bisognoso. C'è una spiegazione di come funziona questo metodo http://blog.arkency.com/2013/12/rails4-preloading/. Puoi chiamare direttamente eager_load(:events) e penso che caricherà i tuoi oggetti AR senza n + 1 problema.

1

per risolvere il problema della data dinamica, avete considerato:

class Event < ActiveRecord::Base 

    belongs_to :place 

    scope :on_date, lambda {|the_date| where(start_date: the_date) } 
    scope :on_or_after, lambda {|the_date| where('start_date >= ?', the_date) } 
end 

si potrebbe quindi fare questo:

@place = Place.find(params[:id]) # let's say... 
@place.events.on_date(params[:chosen_date]) 

È possibile incorporare la roba eager loading che altri hanno detto troppo.