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.