Eager Loading: the Right Way to Do Things

What is the proper way to eager load associated objects for a 'has_many' association?

You should use includes method, like in following:

Article.includes(:comments).where(title: 'Sample text')

For your first case, you don't really need eager loading, since you only select one record, so this is not n+1 problem. You can just do:

article = Article.find(1)
article.comments

If you use include your articles frequently, you may define scope:

scope :with_comments, -> { includes(:comments }

and simply call it:

Article.with_comments.where(title: 'Sample text')

When your relation into which you want to include comments comes from another association, nothing changes, you can do both:

@current_user.articles.includes(:comments)

and

@current_user.articles.with_comments

How does eager loading work? I mean I know what it 'does' but can I replicated it by doing a 'side' query?

Eager loading is doing something very similar to what you are doing, but in addition to simply querying the database, it's also establishing relationships. When you retrieve a model from the database that has related models, the related models are generally loaded on demand. The example from the guides is:

clients = Client.limit(10)

clients.each do |client|
puts client.address.postcode
end

When you do the first query, there's a call to the database to grab ten clients. As you iterate through them to get the postcode that is associated with a client address (which presumably lives in a different table), you'll need to do 10 more queries to grab the proper addresses. By using eager loading, you can do the whole thing in just two queries (which are handled under the covers).

clients = Client.includes(:address).limit(10)

This grabs the ten clients, then does another query (similar to yours) to grab the addresses that are in the array of client_id's. These are then resident in memory, with relationships in tact, so you don't need to make another database call to get them.

If you tried to replicate this by just loading them seperately:

clients = Client.limit(10)
addresses = Address.where("client_id in [my ID array]")
clients.each do |client|
puts client.address.postcode
end

You're still going to be hitting the database for each of the addresses, since those objects aren't associated with each other except through the database id's. The objects are loaded, but the relationships aren't created between them.

Trouble on eager loading and using the 'where' method

In case 1 the where clause only applies to categories. If you also want it to apply to comments you can do this:

article_categories =
article
.categories
.includes(:comments)
.where('categories.user_id = ? AND comments.user_id = ?', @current_user.id, @current_user.id)

How to eager load an association after eager loading on the parent?

I'm posting this an answer to make sure that it's clear it's been resolved. Once I'm capable I will accept this as the correct answer.

Baldrick's comment got me to thinking about how exactly I was filtering out activities for the user so that I only had the activities logged to the active organization. I was performing:

user.activities.where("organization_id = ?", org.id)

Which would force a query every time regardless of what I've done. So I changed it to:

user.activities.select { |o| o.id == org.id }

Which now performs what I've wanted (after @Baldrick's recommendation as well).

Lazy Loading vs Eager Loading

I think it is good to categorize relations like this

When to use eager loading

  1. In "one side" of one-to-many relations that you sure are used every where with main entity. like User property of an Article. Category property of a Product.
  2. Generally When relations are not too much and eager loading will be good practice to reduce further queries on server.

When to use lazy loading

  1. Almost on every "collection side" of one-to-many relations. like Articles of User or Products of a Category
  2. You exactly know that you will not need a property instantly.

Note: like Transcendent said there may be disposal problem with lazy loading.



Related Topics



Leave a reply



Submit