DRYing Up Preloads in Rails
As a Rails application grows, I’ve found that this pattern often emerges where we are repeating the same preloads across the application.
Post.preload(:user, :category, comments: { user: :avatar }).where(something: true).limit(100)
Whenever we add an association, or make a change. We have to update our preloads in multiple places. If we forget one, we’ve just introduced a potentially severe performance problem in our application.
(If you’re not familiar with includes/preloads/n+1’s, read this.)
We can DRY up this code by extracting it to a scope on the model.
We’ve been using this pattern in Product Hunt’s codebase for a few months now and I like it a lot.
scope :with_preloads, -> { preload preload_attributes }
class << self
def preload_attributes
[:user, :category, comments: { user: :avatar }]
end
end
Now in our code, we can use our new scope like this.
# Old 😞
Post.preload(:user, :category, comments: { user: :avatar }).where(something: true).limit(100)
# New & Improved 😎
Post.with_preloads.where(something: true).limit(100)
Another benefit of adding this scope to all of our models is that it starts to look strange to see a query without with_preloads. It acts as a nice reminder to double check everything is being correctly loaded.