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.

class Article < ApplicationRecord
  has_many :comments
end

class Comment < ApplicationRecord
  belongs_to :article
end

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.

2.7.2 :001 > article = Article.strict_loading.first
  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
 => #<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">
2.7.2 :002 > article.strict_loading?
 => true
2.7.2 :003 > article.comments
Traceback (most recent call last):
ActiveRecord::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.

2.7.2 :004 > article = Article.includes(:comments).strict_loading.first
  Article Load (0.7ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
 => #<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">
2.7.2 :005 > article.comments
 => #<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.

2.7.2 :006 > article.comments.all?(&:strict_loading?)
 => 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.

class Article < ApplicationRecord
  has_many :comments, strict_loading: true
end

class Comment < ApplicationRecord
  belongs_to :article
end

Let's verify this in Rails console.

2.7.2 :001 > article = Article.first
  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
 => #<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">
2.7.2 :002 > article.strict_loading?
 => false
2.7.2 :003 > article.comments
Traceback (most recent call last):
ActiveRecord::StrictLoadingViolationError (`comments` called on `Article` is marked for strict_loading and cannot be lazily loaded.)


2.7.2 :004 > article = Article.includes(:comments).first
  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
 => #<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">
2.7.2 :005 > article.comments
 => #<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.

class Article < ApplicationRecord
  self.strict_loading_by_default = true

  has_many :comments
end

class Comment < ApplicationRecord
  belongs_to :article
end

Let's verify this setting in the Article model.

2.7.2 :001 > article = Article.includes(:comments).first
  Article Load (0.2ms)  SELECT "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
 => #<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">
2.7.2 :002 > article.strict_loading?
 => true
2.7.2 :003 > article.comments.all?(&:strict_loading?)
 => false
2.7.2 :004 > article.comments
 => #<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.

config.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.

config.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
06, 2021
Share

You might also like

Rails 6.1 adds support for PostgreSQL interval data type

Rails 6.1 allows per environment configuration support for Active Storage

Rails 6.1 adds support for belongs_to to has_many inversing

Rails 6.1 adds where.associated to check association presence

Subscribe to our newsletter