0

Apparently in my Rails application ActiveRecord is running more SQL queries than I would like. I have a simple one-to-many relation. For each package…

class Package < ActiveRecord::Base
  has_many :screenshots
end

…there are several screenshots…

class Screenshot < ActiveRecord::Base
  belongs_to :package

  def image_url
    "/screenshots/#{self.package.name}/#{self.id}_#{size}.png"
  end
end

My first simple task is to show an index page with packages and screenshots. For each package I want to show its screenshots. The path to a screenshot image is determined by the package name. Let's do an example and fetch the package record for the "3dchess" package:

pkg = Package.includes(:screenshots).find_by_name('3dchess')

As you can see I already eager-load the screenshots. ActiveRecord runs these two SQL queries:

  Package Load (1.4ms)  SELECT "packages".* FROM "packages" WHERE "packages"."name" = '3dchess' LIMIT 1
  Screenshot Load (2.1ms)  SELECT "screenshots".* FROM "screenshots" WHERE "screenshots"."package_id" IN (243)

I had expected that this could be done in one query but I don't mind the two. But when I'm displaying 20 screenshots on an index page I suddenly get 20 additional SQL queries. And it turns out that they are caused by the virtual attribute "image_url" of the Screenshot model.

What I'm doing in my template:

<% @packages.each do |package| %>
  <li>
    <% if package.screenshots %>
      <%= image_tag package.screenshots.first.image_url %>
    <% end %>
  </li>
<% end %>

And each call of "image_tag package.screenshots.first.image_url" runs a SQL query like:

SELECT "packages".* FROM "packages" WHERE "packages"."id" = $1 ORDER BY "packages"."id" ASC LIMIT 1

So although I'm eagerloading the screenshots when I'm loading a package - it does not work the other way round.

Or to put it another way - this creates the two SQL queries mentioned above:

pkg = Package.includes(:screenshots).find_by_name('3dchess')

But when I try to access

pkg.package

then ActiveRecord runs an additional query getting the "package" information although it should already have them.

So my question is: how can I make ActiveRecord eager-load the backreference from "screenshot" to "package" automatically?

  • I just found that :inverse_of seems to prevent the extra SQL queries. But that seems to be a pretty hidden option. Is that really the correct way to eager-load the backreferences? – Christoph Haas Aug 07 '13 at 19:34
  • I noticed you are using two variables. `pkg` and `@packages`. Lets use only single variable `@packages` and see what happens. – Krishna Rani Sahoo Aug 08 '13 at 09:52
  • krishnasahoo: I admit that I left off parts from my code. The controller's action actually sets: @packages = Package.includes(:screenshots) – Christoph Haas Aug 08 '13 at 17:39

0 Answers0