Ben Smith
Ben Smith

Reputation: 861

Rails eager loading all records but still generating N+1 queries

I'm using Rails 4.1 and have a very basic problem getting eager loading to work.

I've simplified a complex program into a very basic example of events, users and reservations:

The event model:

class Event < ActiveRecord::Base
  has_many :reservations

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

The user model:

class User < ActiveRecord::Base
  has_many :reservations
end

The reservation model:

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

My event controller:

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

My views/events/index.html.erb view:

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

I've created a user and three events. The resulting server log for the event index page:

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)

As you can see, rails correctly eager loads all the reservations for my events. However it then proceeds to query the database again during the reserved?(user) call. What am I doing wrong? Thanks.

Upvotes: 2

Views: 1120

Answers (1)

Charles Treatman
Charles Treatman

Reputation: 558

Your reserved? method uses the where method of the reservations association. The where method is an ARel method that is used to build SQL queries. Calling present? on the result of the where method causes the query to be executed.

To avoid doing another database lookup in reserved?, you should use a Ruby collection method instead, for example:

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

Or, for a more concise version (someone added this as a comment, but it looks like it may have been deleted):

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

Upvotes: 3

Related Questions