こんにちは。
GMOアドマーケティングの石丸(@thomi40)です。
昨年の12月にRuby on Rails 6.1の新機能の1つである「annotate_rendered_view_with_filenames」について紹介しました。
今回は、同じくRuby on Rails 6.1で追加された「strict_loading」について紹介します。
strict_loadingとは?
strict_loading
はRails 6.1から実装された機能で、主に関連付け(Association)で発生する遅延読み込み(Lazy Loading)を防ぐ目的で追加されました。
この機能を活用することで、N+1問題を防ぐことが期待できます。
では実際に試してみましょう。
検証用の環境構築
検証のために簡易的なアプリケーションを作成します。
今回はDocker環境でRubyは2.7.3、Railsは6.1.3.2を指定しました。
アプリを立ち上げた後に、以下のコマンドで雛形を作成します。
1 2 |
$ docker-compose run web rails g scaffold user name:string email:string password_digest:string $ docker-compose run web rails g scaffold task name:string user:references |
モデルは以下の通りです。
1 2 3 |
class Task < ApplicationRecord belongs_to :user end |
1 2 3 |
class User < ApplicationRecord has_many :tasks end |
N+1を発生させるためにサンプル用のデータをコンソールから作成します。
1 2 3 4 5 6 7 |
user = User.create(name: 'taro', email: 'xxx@gmo-am.jp') user.tasks.create(name: 'task001') user.tasks.create(name: 'task002') user = User.create(name: 'hana', email: 'xxx@gmo-ap.jp') user.tasks.create(name: 'task003') user.tasks.create(name: 'task004') |
viewとcontrollerはscaffoldで作成されたものからほぼ変更はありませんが、viewに関してはN+1を発生させるために以下のように修正しています(以下のコード6行目部分)。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!--省略--> <% @users.each do |user| %> <tr> <td><%= user.name %></td> <!--関連するテーブルを参照--> <td><%= user.tasks.map(&:name) %></td> <td><%= user.password_digest %></td> <td><%= link_to 'Show', user %></td> <td><%= link_to 'Edit', edit_user_path(user) %></td> <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> <!--省略--> |
strict_loadingを試す
環境が整ったので、実際にstrict_loadingを試してみます。
strict_loadingを設定する方法はいくつかありますが、今回は関連付けのオプションとして設定し、N+1が発生する http://localhost:3000/users
へアクセスしてみます。
1 2 3 |
class User < ApplicationRecord has_many :tasks, strict_loading: true end |
eager loadingを行っていないため、 StrictLoadingViolationError
が発生しました。
期待通りに動作していますね。
エラーを回避するために、controllerで @users = User.all
となっていた部分を以下のように変更します。
1 2 3 |
def index @users = User.includes(:tasks) end |
改めて http://localhost:3000/users
へアクセスすると正常にアクセスできました。
もしエラーではなくログに表示したい場合は環境に合わせてenvironmentsに以下を加えることで、ログとしてLazy Loadingを検知することも可能です。
1 |
config.active_record.action_on_strict_loading_violation = :log |
今回の環境で検証すると以下のようなログが出力されました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Processing by UsersController#index as HTML Rendering layout layouts/application.html.erb Rendering users/index.html.erb within layouts/application User Load (75.8ms) SELECT `users`.* FROM `users` ↳ app/views/users/index.html.erb:17 Strict loading violation: User is marked for strict loading. The Task association named :tasks cannot be lazily loaded. ↳ app/views/users/index.html.erb:21:in `map' Task Load (2.9ms) SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`user_id` = 1 ↳ app/views/users/index.html.erb:21:in `map' Strict loading violation: User is marked for strict loading. The Task association named :tasks cannot be lazily loaded. ↳ app/views/users/index.html.erb:21:in `map' Task Load (2.6ms) SELECT `tasks`.* FROM `tasks` WHERE `tasks`.`user_id` = 2 ↳ app/views/users/index.html.erb:21:in `map' Rendered users/index.html.erb within layouts/application (Duration: 557.5ms | Allocations: 10604) |
用途に合わせて検知方法をカスタマイズできるのは使いやすくていいですね。
まとめ
今回はLazy Loadingを検知する「strict_loading」について紹介しました。
N+1を検知する方法は他にもありますが、Railsに標準で実装されたのは心強いですね。
strict_loadingの使い方は他にもたくさんあるので、気になる方は以下の参考リンクにアクセスしてみて下さい。
参考リンク
Strict loading in Active Record and more | Riding Rails (参照: 2021年5月27日)
Add strict_loading
mode to optionally prevent lazy loading by eileencodes · Pull Request #37400 · rails/rails (参照: 2021年5月27日)
Support strict_loading on association declarations by kddeisz · Pull Request #38541 · rails/rails (参照: 2021年5月27日)
Allow to enable/disable strict_loading mode by default for a model. by bogdanvlviv · Pull Request #39491 · rails/rails (参照: 2021年5月27日)
2016卒のWebエンジニア。
採用やマネジメントもやってます。