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