本稿は、Ateam Finergy Inc. Advent Calendar2020の15日目の記事です。
本稿では、Rails6.1で新たに追加されたstrict_loading
について解説します。
はじめに
strict_loading
は一言で言うと、N+1の発生を予防するためにActive Recordに導入された機能です。
以下のようなassociationのmodelがある場合、実際にどのようにして使うのか見ていきます。
class User < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
end
メソッドとして使う
strict_loading
メソッドを使って取得したrecordでassociationを呼び出すと、以下のようにActiveRecord::StrictLoadingViolationError
が発生します。
user = User.strict_loading.first
user.posts
=> ActiveRecord::StrictLoadingViolationError (`Post` called on `User` is marked for strict_loading and cannot be lazily loaded.)
includes
などEager loadingを行うメソッドでposts
を指定しておくと、問題なく呼び出すことができます。
user = User.includes(:posts).strict_loading.first
user.posts
=> #<ActiveRecord::Associations::CollectionProxy [#<Post id: 1, user_id: 1, title: "post_1", created_at: "2020-12-06 16:08:23.688506000 +0000", updated_at: "2020-12-06 16:08:23.688506000 +0000">, #<Post id: 2, user_id: 1, title: "post_2", created_at: "2020-12-06 16:08:23.696069000 +0000", updated_at: "2020-12-06 16:08:23.696069000 +0000">]>
上記のようにEager loadingを行うことが必須となるため、N+1の発生を予防することができます。
associationに定義する
strict_loading
は、associationを定義する際にもオプションとして指定することができます。
class User < ApplicationRecord
has_many :posts, strict_loading: true
end
こうしておくと、record取得の際にstrict_loading
メソッドを使わなくてもActiveRecord::StrictLoadingViolationError
が発生します。
user = User.first
user.posts
=> ActiveRecord::StrictLoadingViolationError (`posts` called on `User` is marked for strict_loading and cannot be lazily loaded.)
model単位で指定する
association定義1つずつに指定するのが面倒な場合は、model単位で全てのassociationをまとめて指定することもできます。
class User < ApplicationRecord
self.strict_loading_by_default = true
has_many :posts
end
user = User.first
user.posts
=> ActiveRecord::StrictLoadingViolationError (`Post` called on `User` is marked for strict_loading and cannot be lazily loaded.)
ログに出力する
ActiveRecord::StrictLoadingViolationError
のエラーをraiseせずに、ログ出力することもできます。
config.active_record.action_on_strict_loading_violation = :log
デフォルトではエラーをraiseしますが、このようにconfigで各環境ごとにログ出力を指定できます。
Active Storageで使う
Active Storageのassociationであるhas_one_attached
とhas_many_attached
に指定することもできます。
class Post < ApplicationRecord
belongs_to :user
has_one_attached :logo, strict_loading: true
has_many_attached :images, strict_loading: true
end
終わりに
N+1は、Railsのパフォーマンスを向上させる上で避けては通れない問題だと思います。
Rails6.1から新たに導入されたこのstrict_loading
を上手く活用することでN+1の発生を予防して、より高パフォーマンスのRailsアプリケーションが多く構築されることを願っております。
参考
- Add
strict_loading
mode to optionally prevent lazy loading by eileencodes · Pull Request #37400 · rails/rails - Support strict_loading on association declarations by kddeisz · Pull Request #38541 · rails/rails
- Clarify docs about strict loading and add a test case for
strict_loading!
by bogdanvlviv · Pull Request #39490 · rails/rails - Allow to enable/disable strict_loading mode by default for a model. by bogdanvlviv · Pull Request #39491 · rails/rails
- Allow applications to change the behavior for a strict loading violation by eileencodes · Pull Request #40511 · rails/rails
- Add strict loading for active storage by dcangulo · Pull Request #40623 · rails/rails
- 6.1.0.rc1 Strict loading assocations doesn't work with ActiveStorage · Issue #40621 · rails/rails