user3366016
user3366016

Reputation: 1312

How can I use eager loaded data after join for each record returned?

In my code I join 2 models (e.g. Post and Comment) then I eager load a third model (e.g. Author). When I print each record rails uses the eager loaded model data for the first instance of Post but triggers additional database queries for each additional instance of Post. This is a bit of an n+1 problem.

What I do not understand is why I'm only able to use the eager loaded data for the first record of each post and not the additional records returned by the join?

I have looked at similar questions and do not feel these address the exact same issue. These include:

I've also looked into the rails source code, specifically ActiveRecord::Associations::Preloader and have tried to modify it a bit locally to see if I could narrow down where the issue is or if it happens here. I had thought that possibly this class was stripping out non-unique record instances by id (which I think it is at line 92) but when I change this my issue is not resolved.

I did stumble across this whacky edge case which seemed applicable but in my actual code I'm using find_by_sql and have not been able to implement this effectively.

I have created a gist with steps to reproduce the issue I'm experiencing and the output I'm getting. Any help would be appreciated.

Note: This example is the simplest setup I could think of that demonstrates the same issue I'm having in my actual code. In my example case I know I could eager load the comments and authors however, in my actual code there is a much more complex join and the join is required. I would not be able to eager load the Comment model from this example.

Upvotes: 0

Views: 70

Answers (1)

user3366016
user3366016

Reputation: 1312

For those that find this in the future, here is what I've found and what I implemented.

Simple gist example

For the simple example outlined in my gist, I found that I can get the desired results be changing the following.

Gist:

# Triggers n+1 for author after first puts of an author.
posts = Post.joins(:comments).select("posts.*, comments.body").includes(:author)
posts.each do |x|
  puts "#{x.title} #{x.author.name} #{x.body}"
end

Changed to:

# Works as expected.
posts = Post.joins(:comments, :author).select("posts.*, comments.body").includes(:author)
posts.each do |x|
  puts "#{x.title} #{x.author.name} #{x.body}"
end

By joining the author I get the results I wanted without additional queries.

More complex example and Implementation

Although the above solved my simple example I used to demonstrate my problem, in my more complex real-world problem (as noted in my question) I was not able to implement this additional join to solve things.

Instead I implemented the Query Object Pattern and Decorator Pattern as outlined in this great Thoughbot post and in the rails handbook on github (Query Object Pattern and Decorator Pattern).

These patterns allowed me to get all the benefits of eager loading the specific associations I wanted but for my much more complex query.

Upvotes: 0

Related Questions