2014-05-20 12 views
12

Ich habe ein Place-Modell und ein Event-Modell. Orte können Ereignisse haben, die stattfinden an einem bestimmten Datum.Preload has_many Assoziationen mit dynamischen Bedingungen

Wie kann ich meine Verknüpfungen und Finder einrichten, um alle Orte einschließlich (eifrig laden) ihrer Ereignisse zu einem bestimmten Datum ohne N + 1 Abfrageproblem zu laden?

Was ich versucht habe:

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). 

ich erfolgreich mit einem Verband kam das tut, was ich will, aber ist nicht dynamisch (akzeptiert keine Parameter)

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. 

Das Problem für mich ist, dass ich keinen Weg finden kann, um mit dynamischen Bedingungen vorzuladen/eifrig zu laden. Weil preload und includes den Assoziationsnamen als Parameter erwarten und nicht mit Parametern verfeinert werden können. Zumindest habe ich keine Möglichkeit gefunden, dies zu tun.

+1

Dies ist ein schöner Artikel erklärt eager loading in Rails: http://blog.arkency.com/2013/12/rails4-preloading/ – MurifoX

+0

Danke, aber schon diesen Artikel lesen ein paar von Zeiten aber konnte keine Informationen über dynamische Bedingungen beim Vorladen finden. – nvano

+0

Es kann auch keine Möglichkeit gefunden werden, Argumente an ActiveRecord :: Associations :: Preloader zu übergeben. Überschreiben oder Unterklassen scheint ein falscher Ansatz zu sein. – nvano

Antwort

3

Dies scheint die einzige Lösung zu sein, die funktioniert:

# 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 

Es ist ein bisschen hacky, aber es ist ganz leicht an jede Situation anzupassen, wo Sie eine Verbindung mit einer separaten Abfrage möchten laden und dann befestigen zu einem vorhandenen Objekt.

Alle Kredit geht an https://mrbrdo.wordpress.com/2013/09/25/manually-preloading-associations-in-rails-using-custom-scopessql/

+1

Einfach unglaublich ... endlich etwas, das funktioniert. Eine Sache - "to_a" im ersten Schritt wird Sie daran hindern, zusätzliche Abfragen zu verketten usw., aber Sie müssen dort keine Orte in ein Array konvertieren. –

+0

Die erste Abfrage ist nur ein Beispiel, Sie müssen nicht explizit in Array konvertieren ('map' führt die Ausführung trotzdem aus). Sie können auch zusätzliche Filter hinzufügen, wenn Sie möchten. Du hast die Idee. –

0

Wie ich verstehe, möchten Sie alle Orte holen, die mindestens ein Ereignis hat, das eine Bedingung erfüllt, aber Orte sollten mit allen Ereignislisten abgerufen werden, selbst wenn die Bedingung nicht erfüllt ist. Sie können nicht herausfinden, dass dies mit einer einfachen Abfrage erledigt ist, aber wenn Sie Suquery verwenden, wird das Problem gelöst. Hier ist die Lösung:

Es wird eine komplexe Abfrage erstellt werden, aber es tut die Dinge richtig.

0

Ich habe in einigen Fällen erwähnt, includes nicht richtig wählen eifrig laden Methode. Es gibt eine Erklärung, wie diese Methode funktioniert http://blog.arkency.com/2013/12/rails4-preloading/. Sie können eager_load(:events) direkt anrufen und ich denke, es wird Ihre AR-Objekte ohne n + 1 Problem laden.

1

das dynamische Datum Problem zu lösen, haben Sie darüber nachgedacht:

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 

Sie dann dies tun könnte:

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

Sie das Zeug eifrig Laden übernehmen können, die auch andere erwähnt haben.