Lazy loading associations for the ActiveRecord models
ArLazyPreload is a gem that brings association lazy load functionality to your Rails applications. There is a number of built-in methods to solve N+1 problem, but sometimes a list of associations to preload is not obvious–this is when you can get most of this gem.
#lazy_preloadinstead of
#includes,
#eager_loador
#preload
TASK=benchand
TASK=memory)
ArLazyPreload.config.auto_preloadto
true
Lazy loading is super helpful when the list of associations to load is determined dynamically. For instance, in GraphQL this list comes from the API client, and you'll have to inspect the selection set to find out what associations are going to be used.
This gem uses a different approach: it won't load anything until the association is called for a first time. When it happens–it loads all the associated records for all records from the initial relation in a single query.
Let's try
#lazy_preloadin action! The following code will perform a single SQL request (because we've never accessed posts):
users = User.lazy_preload(:posts).limit(10) # => SELECT * FROM users LIMIT 10 users.map(&:first_name)
However, when we try to load posts, there will be one more request for posts:
users.map(&:posts) # => SELECT * FROM posts WHERE user_id in (...)
If you want the gem to be even lazier–you can configure it to load all the associations lazily without specifying them explicitly. To do that you'll need to change the configuration in the following way:
ArLazyPreload.config.auto_preload = true
After that there is no need to call
#lazy_preloadon the association, everything would be loaded lazily.
If you want to turn automatic preload off for a specific record, you can call
.skip_preloadbefore any associations method:
users.first.skip_preload.posts # => SELECT * FROM posts WHERE user_id = ?
Another alternative for auto preloading is using relation
#preload_associations_lazilymethod
posts = User.preload_associations_lazily.flat_map(&:posts) # => SELECT * FROM users LIMIT 10 # => SELECT * FROM posts WHERE user_id in (...)
.includesis called earlier:
Post.includes(:user).preload_associations_lazily.each do |p| p.user.comments.load end
#sizeis called on association (e.g.,
User.lazy_preload(:posts).map { |u| u.posts.size }), lazy preloading won't happen, because
#sizemethod performs
SELECT COUNT()database request instead of loading the association when association haven't been loaded yet (here is the issue, and here is the explanation article about
size,
lengthand
count).
Add this line to your application's Gemfile, and you're all set:
gem "ar_lazy_preload"
The gem is available as open source under the terms of the MIT License.