Every once in a while when I’m working with Rails, I face a little
problem like this: given two models, Book and Category, book belonging to a
category, list all books ordered by its category’s name, showing it
alongside with the it’s name.
That’s easy enough, right?
The code above generates the SQL below:
I don’t like this SQL. If I have a lot of books and categories, its
performance will not be as good as it could. Let’s make it better.
The SQL now will be:
Despite the fact that two queries need to be run for this approach, it is, in
fact, better than the former. It’s faster, when you have a large amount of
data.
Very well. Now, let’s create a fourth book:
Did you notice something wrong? The fourth book is not shown on the
list. That’s because it has no category. The INNER JOIN
generated by ActiveRecord excludes this record from the query.
A Known Solution
The solution here, as you might know, is to use LEFT JOIN instead of
INNER JOIN. The first algorithm shown here uses it LEFT JOIN, but it’s
slower.
Digging on Google brought me to this solution:
The pros:
It works;
it’s faster than the eager_load.
But it looks terrible, don’t you think? The cons:
It’s not database agnostic;
I have to explicitly define in raw SQL how these two tables are called and
how they are related to each other;
It’s not DRY, since I already defined their names and relashionship in the
model.
Arel to the Rescue!
I found another solution:
The pros:
It’s database agnostic;
I do not need to know the tables’ names.
The cons:
It’s too much code to do too little;
I still have to specify how the relashionship is done.
Based on the Arel solution, I created a gem
that adds this functionality to ActiveRecord:
Now, the version using the gem:
Much cleaner, right? The advantages:
It’s faster than eager_load;
It’s database agnostic;
I don’t need to write any raw SQL;
I don’t need to repeat information about tables or relationships;
It works like joins does, so it’s possible to LEFT JOIN a chain of
associations.