January 6, 2021
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
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
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">]>
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.
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">]>
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
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.
If this blog was helpful, check out our full blog archive.