ActiveRecord Eager Loading
This article was written as part of the team at Next Empire.
Seamless ActiveRecord Eager Loading with Query Objects and Decorators
We were recently in the situation where we wanted to query all of a
User’s friends that attended the same
Events. The query is somewhat complicated because of the two many-to-many relationships (user’s friends and event’s attendees). In this article, I’ll explain the solution we’ve developed that did not require any changes at the view level.
The basic models looked like this:
For fetching the attending friends, a method was added to
Event, as such:
Note that the
attending_friends_for_user could’ve been implemented as a named scope as well, but using a method makes it easier to optimize later.
With this method, we can now easily show attending friends for a particular user.
This works, but it poses a problem when the number of events gets large. A query is executed for every individual event, resulting in N+1 queries being executed for this list (one for fetching a list of events, and then one for every event). Such a design can result in really slow requests in production.
To solve the N+1 query problem, we took the approach by Thoughtbot. Many thanks to Thoughtbot for publishing their article. It was instrumental for designing our solution. We think we improved upon the Thoughtbot implementation in such a way that views do not need to be updated. This decreases the coupling of controllers/views which is generally good for portability of code.
Like Thoughtbot, we make use of a
Query Object and a
The Query Object
The Query Object is defined as follows:
We extended the
Enumerable, so we seamlessly iterate over the feed and we don’t need to adapt our view as defined earlier.
Note that the instance variable of the feed is still called
@events, such that the view can access it like a usual query result.
We create the decorator as a subclass of
SimpleDecorator and apply this decorator to all events in the feed.
As-is, the decorator does nothing but delegate
attending_friends_for_user back to the
Event class, but this presents the possibility to implement eager loading here, abstracted away from the
Implementing eager loading
In the decorator we overwrite the
attending_friends_for_user(user) method. Here we return an eager loaded result if available and otherwise fall-back to the
Event class’ implementation. This function is defined as
Fetching the events and the associated friends now requires only 2 queries and will run noticeably faster with large numbers of events. The implementation nicely abstracts the eager loading away from the
Since we had other objects where we wanted the same ability to fetch friends, we ended up moving the functionality into a
WithFriends concern which could then simply be included by