views:

73

answers:

1

I'm a little confused as to the mechanics of eager loading in active record. Lets say a Book model has many Pages and I fetch a book using this query:

@book = Book.find book_id, :include => :pages

Now this where I'm confused. My understanding is that @book.pages is already loaded and won't execute another query. But suppose I want to find a specific page, what would I do?

@book.pages.find page_id

# OR...

@book.pages.to_ary.find{|p| p.id == page_id}

Am I right in thinking that the first example will execute another query, and therefore making the eager loading pointless, or is active record clever enough to know that it doesn't need to do another query?

Also, my second question, is there an argument that in some cases eager loading is more intensive on the database and sometimes multiple small queries will be more efficient that a single large query?

Thanks for your thoughts.

+1  A: 

When ActiveRecord eagerly loads an association, what happens deep in the bowels of your object is an instance variable being set. That's it, no magic. All methods provided by Enumerable and the ActiveRecord::AssociationProxy simply look at the instance variable to know if it was loaded or not, and then proceeding with it's business.

When you call #find, you're not loading the collection: you're searching for a particular instance. You're aren't talking to the collection itself.

Your second example would be the way to go, but I would do it differently:

@book.pages.detect {|p| p.id == page_id}

Alternatively, and here I'm assuming some things about your application, I would do it in an even better way:

class BooksController < ApplicationController
  def show
    @book = Book.find(params[:id], :include => :pages)
    @pages_by_id = @book.pages.index_by(&:id)
  end
end

# app/views/books/show.html.erb
Page: <%= @pages_by_id[page_id].number %>

Notice I'm using #index_by, which returns a Hash where the keys are the result of evaluating a block, and the values are the original object. Since you seem to only want to find pages by ID, it makes sense to have a Hash of those.

Your point about being more or less intensive on the database is a good one, and one that bears keeping in mind at all times. If you'll use most of the returned data, it will make sense. If you only use a tiny subset of the data, it doesn't make sense to download all the data for those objects, only to throw them at the garbage collection milliseconds later.

François Beausoleil