パーシャルをrenderする際のパフォーマンスに関する注意点


ビュー内で部分テンプレート（パーシャル）を繰り返しrenderする場合、ちょっと気をつけておかねばならないことがあります。自戒の念を込めて、Qiitaに初投稿してみたいと思います。

####検証した環境

* Rails 4.2 (3.2系でも同様だったはず）

# コレクションを繰り返しrenderする場合

可能な限りrenderメソッドにはコレクションを渡しましょう。each等でぐるぐる回してrenderしても表示結果は変わりませんが、**パフォーマンスは悪い**です。

```erb
# × 悪い例
<% @posts.each do |post| %>
  <%= render post %>
  # もちろん、↓こう書いても
  <%= render 'posts/post', post: post %>
  # ↓こう書いてもダメ
  <%= render partial: 'posts/post', locals: { post: post } %>
<% end %>
```
```erb
# ◯ 良い例
<%= render @posts %>
# 冗長だが、↓これでも良い
<%= render partial: 'posts/post', collection: @posts %>
```

なんとなくボーッとコーディングしてると、繰り返し？→じゃあeachじゃん、と条件反射的に書いちゃってる場合があります。どれぐらいパフォーマンスに差が出るのか、1,000件のpostsをそれぞれの方法でrenderしてみました。

|     |development|production|
|-----|----------:|---------:|
|悪い例|2,500 ms   |250 ms    |
|良い例|150 ms     |70 ms     |

1,000件もパーシャルをrenderすることはそうそう無いですし、production環境だと劇的に遅くなるわけでもありません。しかし、development環境では差が結構出ますし、パフォーマンスは少しでも速いに越したことはありませんし、何より**railsが用意してくれているベストな方法をチョイスしないのは罪**です。コレクションを渡しましょう。

上記例のようにコレクションの中身がActiveRecordモデルじゃない場合も、わざわざコレクションにして渡してあげた方がいいと思います。

```erb
# ↓こうではなくて
<% 1_000.times do |i| %>
  <%= render 'counter', { now: Time.zone.now, index: i } %>
<% end %>

# ↓この方が速い
<%= render partial: 'counter', collection: (1..1_000).to_a, as: :index, locals: { now: Time.zone.now } %>
```

ちなみに、なぜパフォーマンスに差が出るのか少し調査してみたのですが、主にはパーシャルファイルを探して開く部分にあるようです（パーシャルファイルは１つなので、本来は１度しか実行しなくてよいところを、eachの場合は1,000回実行することになる）。developmentが遅いのはデバッグ情報の取得等々かな。

# コレクションを渡せない場合（fields_forとか）

例えば、Postが```belongs_to :blog```となっており、Blogが以下のようになっている場合、

```rb
has_many :posts
accepts_nested_attributes_for :posts
```

ビューでは```fields_for```メソッドを使用することができます。この時、fields_forで描画する内容をパーシャルにしたいことがあると思います。

```erb
<%= form_for @blog do |f| %>
  ...
  <%= f.fields_for :posts do |builder| %>
    <%= render 'posts/form', post: builder.object, f: builder %>
  <% end %>
  ...
<% end %>
```

この場合は先程のケースのようにrenderにコレクションを渡すことができません。考えられる対応方法は・・・

* postsの件数（=fields_forでループされる件数）が大したことが無いのであれば気にしない
* バーシャルにしない

といった感じでしょうか。まあ、諦めろっちゅうことです。
そんな消極的なのは嫌だ！という人は、[def_erb_method](http://docs.ruby-lang.org/ja/2.2.0/method/ERB=3a=3aDefMethod/m/def_erb_method.html)を使用して、パーシャルビューの描画をerbメソッド化してしまうことができます。

```rb
module PostsHelper
  extend ERB::DefMethod

  def_erb_method 'render_post_form(post, f)', "#{Rails.root}/app/views/posts/_post.html.erb"
end
```
```erb
  <%= f.fields_for :posts do |builder| %>
    <%= render_post_form(builder.object, builder).html_safe %>
  <% end %>
```
これでパーシャルを使用しない場合と同程度に高速化されます。ただし上記の実装だと、開発中に```posts/_post.html.erb```の内容を変更しても、サーバを再起動するまでその変更が反映されません。

もう少し汎用的かつサーバ再起動を不要とするために、以下のように実装してみました。erbメソッドを描画中のビューコンテキスト（＝無名クラスのインスタンス）の特異クラスに定義するようにします。

```rb
module ApplicationHelper
  def render_here(path_to_partial, locals = {})
    method_name = "render_here_#{path_to_partial.gsub(%r{[/\.]}, '__')}"
    unless respond_to?(method_name)
      class_eval do # =singleton_class.class_eval（ActiveSupport拡張）
        extend ERB::DefMethod
        def_erb_method("#{method_name}(#{locals.keys.join(',')})", path_to_partial)
      end
    end
    send(method_name, *locals.values).html_safe
  end
end
```
```erb:呼び出し側.html.erb
    <%= f.fields_for :posts do |builder| %>
      <%= render_here "#{Rails.root}/app/views/posts/_post.html.erb", post: builder.object, f: builder %>
    <% end %>
```

簡素な実装ですが、普段使いには問題なく動いています。railsをハックして高速renderをgem化してやろうかと思いましたが、rails特有の間口の広い引数対応が大変そうだったので、ハンカチを噛み締めながら撤退しました。

もっといい方法があれば教えて下さい。

# 参考

* [Railsアプリを『浅く』パフォーマンス・チューニングしてみる（その２）](http://www.ohmyenter.com/?p=105)
* [RubyOnRails を使ってみる 【第 10 回】 パフォーマンスチューニング](http://magazine.rubyist.net/?0019-RubyOnRails)