[Rails] includesメソッドと部分テンプレートを繰り返しrenderする事によってN+1問題を回避する

学習記録

N+1問題とは?

SQLが必要以上に実行されてしまいパフォーマンスが落ちる問題のことです。 それぞれのモデルに、

has_many :boards
belongs_to :user

といった定義を施すことで、関係性を定義することができます。

N+1問題はアソシエーション有りの時に生じる問題であり、上記の例で言うと掲示板の一覧を表示する際にユーザーの名前を取得するために掲示板の数だけSQLが発行されてしまいます。

具体的には次のようなコードになります。

class BoardsController < ApplicationController
  def index
    @boards = Board.all
  end
end
<% @boards.each do |board| %>
  <%= render partial: 'board', locals: { board: board } %>
<% end %>

上記の書き方では「Boardモデルから掲示板一覧情報」を1回取得し、「紐ついたuser_idをuserの数だけ」n回取得しています。 そしてview画面でパーシャルを呼び出し、@boardsをeach文によって繰り返し表示しています。

この方法でも表示はされますが、SQLが多く発行され、遅くなったりパフォーマンスが下がるんですね。

includesメソッドで関連付けを一括読み込みをする

includesは、関連付いたモデルのデータを先に取得するメソッドです。このメソッドを使うことで、Boardモデルからデータを取得する際に、関連するUserモデルのデータもまとめて取得してくれます。

class BoardsController < ApplocationController
  def index
    @boards = Board.all.includes(:user).order(created_at: :desc)
  end
end

部分テンプレートを繰り返しrenderにする

上記のコードではパーシャルを呼び出し、each文によって繰り返し表示されていました。 そこを下記のように変更します。

<%= render @boards %>

だいぶスッキリしましたね。 これは@boardsというふうに複数形にすることによって、Railsが「_board.html.erb」を自動的に探してくれています。

【コラム】部分テンプレートの省略形について

実は単数形インスタンスを渡す場合と複数形インスタンスを渡す場合では書き方が少し異なります。 それらの書き方を順番に見ていきます。

単数形インスタンス変数を渡す

boardsフォルダ内の_board.html.erbを呼び出すとします。そして、@boardをテンプレート内で変数boardとして使用するとします。

①基本形

<%= render partial: 'boards/board', locals: { board: @board } %>

partial: 」以下でファイル名を指定し、「locals: 」以下の記述で、@boardを部分テンプレート内で変数boardとして使用しています。 board = @board ということですね。

②省略形パターン1

<%= render 'boards/board', board: @board %>

単に「partial」と「locals」を省略しただけの形となります。

③省略形パターン2

<%= render @board %>

インスタンス変数とファイル名が同じ場合、上記のような省略形にすることができます。 かなりスマートな記述になりました。

複数形のインスタンスを渡す

boardsフォルダ内の_board.html.erbを呼び出すとします。そして、@boards をテンプレート内で変数boardとして使用するとします。

省略形パターン1

<%= render partial: 'boards/board', collection: @boards %>

collectionオプションは、渡されたインスタンスの要素の分だけそのテンプレートを繰り返し表示する事ができるオプションです。 つまりコレクションの中のインスタンスが部分テンプレートに呼ばれる変数となるため、each文を使用せずに繰り返し処理ができます。

省略形パターン2

<%= render @boards %>

ファイル名と複数形sを除いたインスタンス名が一致しており、かつeach文のように繰り返し表示したい場合は、上記のように省略することができます。 今回の場合だと_board.html.erb@boardsなので、一致しているということになります。

さらに表現を変えると、

<%= render @boards %>

という省略形は、

<% @boards.each do |board| %>
  <%= render partial: 'board', locals: { board: board } %>
<% end %>

上記の形に書き換える事もできます。

少しややこしいと思いますが、renderの書き方にはいろいろあるので整理してみてください。

参考記事

部分テンプレートの使い方を徹底解説!
railsの部分テンプレートの使い方をわかりやすく解説しています。どんな時に使うのか、どういう記述方があるのか、どういったメリットがあるのか、これを読めば全て解決!省略できる書き方やその条件なども詳しく解説しています。

コメント

タイトルとURLをコピーしました