Is there any diferrence between includes(:associations).references(:associations) and eager_load(:associations) in Ruby on Rails 5?

Viewed 66

It seems includes(:associations).references(:associations) and eager_load(:associations) execute exactly the same SQL (LEFT OUTER JOIN) in Rails 5. So when do I need to use includes(:associations).references(:associations) syntax?

For example,

Parent.includes(:children1, :children2).references(:children1).where('(some conditions of children1)')

can be converted to

Parent.eager_load(:children1).preload(:children2).where('(some conditions of children1)')

I think the latter (query using eager_load and preload) is simpler and looks better.

UPDATE

I found a strange behavior in my environment (rails 5.2.4.3).

Even when I includes several associations and references only one of them, all the associations I included are LEFT OUTER JOINed.

For example,

Parent.includes(:c1, :c2, :c3).references(:c1).to_sql

executes a SQL which LEFT OUTER JOINs all of c1, c2, c3. I thought it joins only c1.

1 Answers

Indeed, includes + references ends up being the same as eager_load. Like many things in Rails, you have a few ways of accomplishing the same result, and here you are witnessing that first hand. If I were writing them in a single statement, I would always prefer eager_load because it is more explicit and it is a single function call.

I would also prefer eager_load because I consider references a kind of hack. It says to the SQL generator "Hey, I am referring to this object in a way that you would not otherwise detect, so include it in the JOIN statement" and is generally used when you use a String to pass a SQL fragment as part of the query.

The only time I would use the includes(:associations).references(:associations) syntax is when it was an artifact needed to make the query work and not a statement of intent. The Rails Guide gives this good example:

Article.includes(:comments).where("comments.visible = true").references(:comments)

As for why referencing 1 association causes 3 to be JOIN'ed, I do not know for sure. The code for includes uses heuristics to decide when it is faster to use a JOIN and when it is faster to use 2 separate queries, the first to retrieve the parent and the second to retrieve the associated objects. I was surprised to find how often it is faster to use 2 separate queries. It may be that since the query has to use 1 join anyway, the algorithm figures it will be faster to use 1 big join rather than 3 queries, or it may in general think 1 join is faster than 4 queries.

I would not in general use preload unless I had a strong reason to believe that it was faster than join. I would just use includes alone and let the algorithm decide.

Related