预加载与动态条件的关联

我有Place模型和Event模型。 地方可以 在特定日期举办活动。

如何在没有N + 1查询问题的特定日期设置我的关联和查找器来加载所有地方,包括(急切加载)他们的事件?

我尝试过的:

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

我成功地想出了一个能够做我想要的但不是动态的关联(不接受参数)

 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. 

对我来说问题是我无法找到一种方法来预加载/急切加载动态条件。 因为preload和includes期望关联名称作为参数,并且不能使用参数进行细化。 至少我发现没办法做到这一点。

这似乎是唯一有效的解决方案:

 # 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 

它有点hacky但是很容易适应你可能想要使用单独的查询加载关联然后将它附加到现有对象的任何情况。

所有功劳都归功于https://mrbrdo.wordpress.com/2013/09/25/manually-preloading-associations-in-rails-using-custom-scopessql/

要解决动态日期问题,您考虑过:

 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 

然后你可以这样做:

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

您可以加入其他人提到的热切加载内容。

据我所知,你想要获取至少有一个事件满足某些条件的所有地方,但是应该使用所有事件列表提取地点,即使是那些不满足条件的地方。 您无法通过一个简单的查询来解决这个问题,但如果您将使用suquery,则问题将完成。 这是解决方案:

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

将构建一个复杂的查询,但它做的事情正确。

我在某些情况下提到includes没有正确选择急切加载方法。 有一个解释这个方法如何工作http://blog.arkency.com/2013/12/rails4-preloading/ 。 您可以直接调用eager_load(:events) ,我认为它会加载您的AR对象而不会出现n + 1问题。