💣

<Rails>viewに配列化したActiveRecord::Relationのインスタンスを渡して、無駄なクエリの発行を抑える

2022/07/21に公開

概要

RailsのAction Mailerを使って、自動配信メルマガの送信機能を実装していた。
実務のコードはもちろん使えないので、大雑把な例を示していく。

例えば、以下のようなapp/mailers/customer_mailer.rbがあるとする

class CustomerMailer < ApplicationMailer

  def remind_email
    @user = params[:user]
    @recommended_places = EventPlace.where(recommend_flag: true)
    mail(to: @user.email, subject: 'おすすめの病院')
  end
end

@recommeded_placesをメイラービューで使って、おすすめのイベント会場に関する情報を表示させたい。

@recommeded_placesは、(訳あって)3つの部分テンプレートに入れる。

app/views/customer_mailer/remind_mail.html.erb

<body>
    <h1><%= @user.name %>様、明日はイベントだよ!</h1>
    <p>開催中の他のイベントも載せるね!!!</p>

    <%= render 'hoge', places: @recommended_places %>

    <%= render 'foo', places: @recommended_places %>

    <%= render 'bar', places: @recommended_places %>
</body>

_hoge.html.erb

(省略)
<% places.each do |p|%>
   <p><%= p.name %><p>
<% end %>
(省略)

_foo.html.erb

(省略)
<% places.each do |p|%>
   <p><%= p.address %><p>
<% end %>
(省略)

_bar.html.erb

(省略)
<% places.each do |p|%>
   <p><%= p.tel %><p>
<% end %>
(省略)

places.eachのところでクエリが走る。
1つのviewの中で3つActiveRecord::Relationのインスタンスを使っているので3回クエリが走ってしまう。

このメールは何千通と送られる。そうすると、メール1通が送られるたびにview内でクエリが3回走るので、メールの送信処理が重くなる。

今回の例ではEventPlace.where(recommend_flag: true)というシンプルなクエリだが、実際には複数のテーブルをjoinして、複雑な絞り込みをするコードを書いていた。

重めのActiveRecord::Relationを複数の部分テンプレートに放り込んだせいで、テスト環境で3分程度だったメールの送信時間が30分に爆増した、、、

解決策

@recomended_places = EventPlace.where(recomend_flag: true).to_a

to_aメソッドを使って配列としてviewに渡す。

そうすることでクエリの発行がコントローラ内の1回に収まり、メール送信時間が正常な範囲まで戻った。

また、特定の属性の値が欲しいのであればあらかじめ

@recommended_places = EventPlace.where(recommend_flag: true).pluck(:name)

という感じで必要な値を格納した配列をviewに渡してあげるのがいい。

学び

ActiveRecord::Relationのインスタンスは使い方次第でパフォーマンスの悪化の原因になることがあるので注意する。困ったときは、to_aメソッドを使って配列化したり、必要な値のみ格納した配列をviewに渡す。
だたし、to_aメソッドを使って配列化するとviewでwhere等のメソッドが使えなくなる点には注意。

Discussion