20

I am running Ruby on Rails 3.1. I read the following articles and documentations about eager loading and I would like to find a right way to do things:

  1. Eager Loading Associations [Official documentation]
  2. ActiveRecord::Associations::ClassMethods (see the section "Eager loading of associations") [Official documentation]
  3. Eager loading [Blog article]

The #2 says:

Note that using conditions like Post.includes([:author, :comments]).where(['comments.approved = ?', true]).all can have unintended consequences.

The #3 says that those unintended consequences are (note: examples are pretty the same so I quote the exact text of the blog article but you have to keep in mind the workaround, not the specific implementation):

This query, since it would use a LEFT JOIN, would also discard all posts without a comment with the word “first” on any of it’s comments.

That is, if there are non "associated" objects, the "main associated" object will not be loaded. This is what happens when I try to use eager loading by adding some condition like .where(:category_relationships => {:user_id => @current_user.id}) in a my previous question, but I do not want that to happen.

So (defeatist because I probably can not use the eager loading in my case where condition can not be set in the has_many statement - note that in the above code the @current_user.id is "set dynamically" unlike in examples present in mentioned sites), I would like to know if there are pratiques/techniques/strategies so to limit database queries since I have a "N + 1 problem".

Maybe those pratiques/techniques/strategies are implementable by using the Ruby on Rails framework at all... the #1 says:

Even though Active Record lets you specify conditions on the eager loaded associations just like joins, the recommended way is to use joins instead.

What and how to solve this issue the right way?


Maybe a solution is to retrieve and build myself what needs to be loaded by running specific and separated database queries, but then the problem would be how to "pass" / "associate" / "interpolate" those retrieved "associated" objects to the "main associated" object so that those can be used "a là eager loading" way? That is, how to make possible (see the mentioned question for more information) to use code like @article.comments and get only comments that I eager loaded myself? After my eager loading, is it possible / correct to make something like @article.comments = my_eager_loaded_comments so to "pass"/"associate"/"interpolate" comments to articles?

Community
  • 1
  • 1
Backo
  • 18,291
  • 27
  • 103
  • 170

2 Answers2

6

After my eager loading, is it possible / correct to make something like @article.comments = my_eager_loaded_comments so to "pass"/"associate"/"interpolate" comments to articles?

Yes, it is possible. I do this regularly.

Note that my solution still retrieves ALL the associated objects from the DB. I don't think there is any solution to retrieving just the filtered association objects if your condition is dynamic. My solution focuses on the filtering of the retrieved association objects.

I am assuming the requirement is to get a list of articles and in each article, eager load the comments of only one particular user.

In the Article model:

def self.get_articles_with_comments_of_user( article_ids, user_id )
  articles = Article.where( id: article_ids ).includes( :comments )

  articles.each do |article|
    filtered_comments = article.comments.select { |comment| comment.user_id == user_id }
    article.association("comments").target = filtered_comments
  end

  articles
end

In the collection of articles returned by the above method, the comments association will have only the comments of that particular user.

I hope this is what you are asking for.

Anjan
  • 1,613
  • 1
  • 19
  • 25
  • "I don't think there is any solution to retrieving just the filtered association objects if your condition is dynamic." - Maybe [this](http://stackoverflow.com/a/15543228/867305) is the solution? – thejaz Mar 21 '13 at 09:24
  • In the linked answer, I don't see any dynamic conditions being passed to filter the eager-loading. I may be understanding it wrong. That answer just describes an ordinary `has_many :through` association. How would that solution be used in the context of this question? – Anjan Mar 22 '13 at 06:49
2

From #2 ActiveRecord::Associations::ClassMethods comes this:

If you do want eager load only some members of an association it is usually more natural to include an association which has conditions defined on it:

class Post < ActiveRecord::Base
  has_many :approved_comments,
    :class_name => 'Comment', 
    :conditions => ['approved = ?', true]
end

Post.includes(:approved_comments)

This will load posts and eager load the approved_comments association, which contains only those comments that have been approved.

If I understand this correctly in context, there's an ambiguity when you apply .where() to your main Query with an includes(), and AR applies the where to the whole query limiting your principle results to those that have qualifying associated predicates. But if you scope the predicate to just the association as above, AR understands and gives you all your principle results and those associated objects which match their predicate condition.

dbenhur
  • 20,008
  • 4
  • 48
  • 45
  • 3
    I can not use the `has_many` association as you write since in my case I should state something like `has_many, :conditions => ['user_id = ?', @current_user.id]` (*it is not possible!* ... of course for my knowledge). So I would like to know a solution to that issue at all relating the eager loading functionality. – Backo Mar 12 '12 at 23:54