Rails6.1の新機能 Strict Loading について

本稿は、[Ateam Finergy Inc. Advent Calendar2020](https://qiita.com/advent-calendar/2020/ateam-finergy)の15日目の記事です。
本稿では、Rails6.1で新たに追加された`strict_loading`について解説します。

# はじめに
`strict_loading`は一言で言うと、N+1の発生を予防するためにActive Recordに導入された機能です。
以下のようなassociationのmodelがある場合、実際にどのようにして使うのか見ていきます。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :posts
end
```
```ruby:app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end
```

# メソッドとして使う
`strict_loading`メソッドを使って取得したrecordでassociationを呼び出すと、以下のように`ActiveRecord::StrictLoadingViolationError`が発生します。

```ruby
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`を指定しておくと、問題なく呼び出すことができます。

```ruby
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を定義する際にもオプションとして指定することができます。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :posts, strict_loading: true
end
```
こうしておくと、record取得の際に`strict_loading`メソッドを使わなくても`ActiveRecord::StrictLoadingViolationError`が発生します。

```ruby
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をまとめて指定することもできます。

```ruby:app/models/user.rb
class User < ApplicationRecord
  self.strict_loading_by_default = true

  has_many :posts
end
```
```ruby
user = User.first
user.posts
=> ActiveRecord::StrictLoadingViolationError (`Post` called on `User` is marked for strict_loading and cannot be lazily loaded.)
```

# ログに出力する
`ActiveRecord::StrictLoadingViolationError`のエラーをraiseせずに、ログ出力することもできます。

```ruby
config.active_record.action_on_strict_loading_violation = :log
```
デフォルトではエラーをraiseしますが、このようにconfigで各環境ごとにログ出力を指定できます。

# Active Storageで使う
Active Storageのassociationである`has_one_attached`と`has_many_attached`に指定することもできます。

```ruby:app/models/post.rb
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](https://github.com/rails/rails/pull/37400)
- [Support strict_loading on association declarations by kddeisz · Pull Request #38541 · rails/rails](https://github.com/rails/rails/pull/38541)
- [Clarify docs about strict loading and add a test case for `strict_loading!` by bogdanvlviv · Pull Request #39490 · rails/rails](https://github.com/rails/rails/pull/39490)
- [Allow to enable/disable strict_loading mode by default for a model. by bogdanvlviv · Pull Request #39491 · rails/rails](https://github.com/rails/rails/pull/39491)
- [Allow applications to change the behavior for a strict loading violation by eileencodes · Pull Request #40511 · rails/rails](https://github.com/rails/rails/pull/40511)
- [Add strict loading for active storage by dcangulo · Pull Request #40623 · rails/rails](https://github.com/rails/rails/pull/40623)
- [6.1.0.rc1 Strict loading assocations doesn&#39;t work with ActiveStorage · Issue #40621 · rails/rails](https://github.com/rails/rails/issues/40621)