BigBinary Blog

We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.

Rails 6.1 adds strict_loading to warn lazy loading associations

This blog is part of our Rails 6.1 series.

Rails 6.1 adds strict_loading mode which can be enabled per record, association, model or across the whole application.

strict_loading mode is an optional setup and it helps in finding N+1 queries.

Let's consider the following example.

1class Article < ApplicationRecord
2  has_many :comments
3end
4
5class Comment < ApplicationRecord
6  belongs_to :article
7end

Mark a record for strict_loading

When strict_loading mode is enabled for a record then its associations have to be eager loaded otherwise Rails raises ActiveRecord::StrictLoadingViolationError.

Let's see this use case by setting strict_loading mode for an article record.

12.7.2 :001 > article = Article.strict_loading.first
2  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
3 => #<Article id: 1, title: "First article", content: "First content", created_at: "2020-12-01 07:23:38.446867000 +0000", updated_at: "2020-12-01 07:23:38.446867000 +0000">
42.7.2 :002 > article.strict_loading?
5 => true
62.7.2 :003 > article.comments
7Traceback (most recent call last):
8ActiveRecord::StrictLoadingViolationError (`Comment` called on `Article` is marked for strict_loading and cannot be lazily loaded.)

strict_loading mode forces us to eager load the associated comments by raising the ActiveRecord::StrictLoadingViolationError error.

Let's fix the strict_loading violation error.

12.7.2 :004 > article = Article.includes(:comments).strict_loading.first
2  Article Load (0.7ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
3  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
4 => #<Article id: 1, title: "First article", content: "First content", created_at: "2020-12-01 07:23:38.446867000 +0000", updated_at: "2020-12-01 07:23:38.446867000 +0000">
52.7.2 :005 > article.comments
6 => #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, desc: "Great article", article_id: 1, created_at: "2020-12-01 07:23:58.832869000 +0000", updated_at: "2020-12-01 07:23:58.832869000 +0000">  , #<Comment id: 2, desc: "Well written", article_id: 1, created_at: "2020-12-01 07:24:02.853376000 +0000", updated_at: "2020-12-01 07:24:02.853376000 +0000">]>

strict_loading mode on article record automatically sets strict_loading mode for all the associated comments as well.

Let's verify this in Rails console.

12.7.2 :006 > article.comments.all?(&:strict_loading?)
2 => true

Mark an association for strict_loading

strict_loading mode can be set up for a specific association.

Let's update our example to see strict_loading in action when it is passed as an option to associations.

1class Article < ApplicationRecord
2  has_many :comments, strict_loading: true
3end
4
5class Comment < ApplicationRecord
6  belongs_to :article
7end

Let's verify this in Rails console.

12.7.2 :001 > article = Article.first
2  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
3 => #<Article id: 1, title: "First article", content: "First content", created_at: "2020-12-01 07:23:38.446867000 +0000", updated_at: "2020-12-01 07:23:38.446867000 +0000">
42.7.2 :002 > article.strict_loading?
5 => false
62.7.2 :003 > article.comments
7Traceback (most recent call last):
8ActiveRecord::StrictLoadingViolationError (`comments` called on `Article` is marked for strict_loading and cannot be lazily loaded.)
9
10
112.7.2 :004 > article = Article.includes(:comments).first
12  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
13  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
14 => #<Article id: 1, title: "First article", content: "First content", created_at: "2020-12-01 07:23:38.446867000 +0000", updated_at: "2020-12-01 07:23:38.446867000 +0000">
152.7.2 :005 > article.comments
16 => #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, desc: "Great article", article_id: 1, created_at: "2020-12-01 07:23:58.832869000 +0000", updated_at: "2020-12-01 07:23:58.832869000 +0000">, #<Comment id: 2, desc: "Well written", article_id: 1, created_at: "2020-12-01 07:24:02.853376000 +0000", updated_at: "2020-12-01 07:24:02.853376000 +0000">]>

Configure strict_loading per model

We can set strict_loading_by_default option per model to mark all of its records and assications for strict_loading.

Let's update our example to set strict_loading_by_default for the Article model.

1class Article < ApplicationRecord
2  self.strict_loading_by_default = true
3
4  has_many :comments
5end
6
7class Comment < ApplicationRecord
8  belongs_to :article
9end

Let's verify this setting in the Article model.

12.7.2 :001 > article = Article.includes(:comments).first
2  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
3  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
4 => #<Article id: 1, title: "First article", content: "First content", created_at: "2020-12-01 07:23:38.446867000 +0000", updated_at: "2020-12-01 07:23:38.446867000 +0000">
52.7.2 :002 > article.strict_loading?
6 => true
72.7.2 :003 > article.comments.all?(&:strict_loading?)
8 => false
92.7.2 :004 > article.comments
10 => #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, desc: "Great article", article_id: 1, created_at: "2020-12-01 07:23:58.832869000 +0000", updated_at: "2020-12-01 07:23:58.832869000 +0000">, #<Comment id: 2, desc: "Well written", article_id: 1, created_at: "2020-12-01 07:24:02.853376000 +0000", updated_at: "2020-12-01 07:24:02.853376000 +0000">]>

Make strict_loading default across all models

We can make strict_loading default across all models by adding the following line to the Rails configuration file.

1config.active_record.strict_loading_by_default = true

Configure strict_loading violations to show only in logs

By default, associations marked for strict loading always raise ActiveRecord::StrictLoadingViolationError for lazy loading.

However, we may prefer to log such violations in our production environment instead of raising errors.

We can add the following line to the environment configuration file.

1config.active_record.action_on_strict_loading_violation = :log

Check out pull requests #37400, #38541, #39491 and #40511 for more details.

Dinesh Panda in Rails, Rails 6.1
January 6, 2021
Share

Subscribe to our newsletter