预加载与动态条件的关联

时间:2014-05-20 12:18:57

标签: ruby-on-rails ruby activerecord preloading

我有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期望关联名称作为参数,并且不能使用参数进行细化。至少我发现没有办法做到这一点。

4 个答案:

答案 0 :(得分:3)

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

# 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/

答案 1 :(得分:1)

要解决动态日期问题,请考虑一下:

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

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

答案 2 :(得分:0)

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

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

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

答案 3 :(得分:0)

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