Rails急于加载所有记录,但仍然生成N + 1个查询

时间:2014-12-03 03:37:12

标签: ruby-on-rails eager-loading

我正在使用Rails 4.1,并且有一个非常基本的问题,即急于加载工作。

我已将一个复杂的程序简化为事件,用户和预订的一个非常基本的例子:

event型号:

class Event < ActiveRecord::Base
  has_many :reservations

  def reserved?(user)
    reservations.where(user: user).present?
  end
end

user型号:

class User < ActiveRecord::Base
  has_many :reservations
end

reservation型号:

class Reservation < ActiveRecord::Base
  belongs_to :user
  belongs_to :event
end

我的event controller

def index
  @events = Event.includes(:reservations).all
  @user = User.find(1)
end

我的views/events/index.html.erb观点:

<% @events.each do |event| %>
  <p>Event: <%= event.name %><br>
  Reserved?: <%= event.reserved?(@user) %><br></p>
<% end %>

我创建了一个用户和三个事件。事件索引页的结果server log

Started GET "/events" for 127.0.0.1 at 2014-12-03 11:26:20 +0800
Processing by EventsController#index as HTML
  User Load (0.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 1]]
  Event Load (0.1ms)  SELECT "events".* FROM "events"
  Reservation Load (0.2ms)  SELECT "reservations".* FROM "reservations"  WHERE "reservations"."event_id" IN (1, 2, 3)
  Reservation Load (0.1ms)  SELECT "reservations".* FROM "reservations"  WHERE "reservations"."event_id" = ? AND "reservations"."user_id" = 1  [["event_id", 1]]
  Reservation Load (0.0ms)  SELECT "reservations".* FROM "reservations"  WHERE "reservations"."event_id" = ? AND "reservations"."user_id" = 1  [["event_id", 2]]
  Reservation Load (0.1ms)  SELECT "reservations".* FROM "reservations"  WHERE "reservations"."event_id" = ? AND "reservations"."user_id" = 1  [["event_id", 3]]
  Rendered events/index.html.erb within layouts/application (10.0ms)
Completed 200 OK in 76ms (Views: 68.7ms | ActiveRecord: 1.3ms)

正如您所看到的,rails正确地加载了我的事件的所有预订。但是,它会在reserved?(user)调用期间再次查询数据库。我究竟做错了什么?感谢。

1 个答案:

答案 0 :(得分:2)

您的reserved?方法使用where关联的reservations方法。 where方法是一种用于构建SQL查询的ARel方法。对present?方法的结果调用where会导致执行查询。

为了避免在reserved?中进行另一次数据库查找,您应该使用Ruby集合方法,例如:

def reserved?(user)
  reservations.select { |reservation| reservation.user == user }.empty?
end

或者,对于更简洁的版本(有人将其添加为评论,但看起来它可能已被删除):

def reserved?(user)
  reservations.map(&:user_id).include?(user.id)
end