content_forとprovideの違い

ちょっと調べたのでメモ。

# `content_for` と `provide` の違い
結論から言うと、デフォルトでは両者に違いはないようです。
ただし、RailsのStreamingを利用している場合に違いが出てきます。

# Streamingとは
かなりざっくり説明するとviewのレンダリングの順番を変えることで、ページのヘッダーを先に送信し、その後(レンダリングに時間のかかる)ボディーの部分を別で送信する事を可能にします。このようにすることで、ボディーの受信を待っている間、クライアントはヘッダー部分にあるJSやCSSを先にロードすることができるようになり、その結果表示速度が早くなります。

では、どのような時にStreamningが有効なのでしょうか？
多くの場合、以下のような重いクエリを投げる時にStreamingを使います。

``` ruby
class PostsController
  def index
    @posts = Post.all
    @articles = Article.all
    @plans = Plan.all
    render stream: true
  end
end 
```

この時、`render stream: true`とすることでStreamingを有効にすることができます。


# Streaming時におけるviewの処理順序の変化
Streaming利用時にRailsの挙動はどのように変化するのでしょうか？まずは、通常時のRailsにおけるviewの処理の順序を説明します。

## 通常時におけるviewの処理順序
1. `reander`メソッド呼び出し
2. template(`app/view/plans/show.html.slim`とか)のrender
3. layout(`app/view/layouts/application.html.slim`とか)のrender

では、Streaming利用時にRailsのviewの処理順序はどのようになるのでしょうか？

## Streaming時におけるviewの処理順序
1. `render`メソッドの呼び出し
2. layout(`app/view/layouts/application.html.slim`とか)のrender
3. template(`app/view/plans/show.html.slim`とか)のrender

通常時とStreaming利用時ではlayoutとtemplateのrenderの順序が違うことがわかります。このように、Streaming利用時は先にlayoutをrenderしてlayoutのheader部分をクライアントに送ることで、クライアントがtemplateのreanderを待っている間、JSやCSSを先にロードしておくことができるようになります。

# `content_for`と`provide`の違い
やっと本題に移ります。Streaming時に、`content_for`と`provide`はどのような違いが生まれるのでしょうか？

## ストリーミング時の`content_for`の挙動
通常時の`content_for`と変わりません。そもそも`content_for`は、どういうものなのでしょうか？RailsGuideを見てみると以下のように説明されています。

> content_forメソッドを使用することで、コンテンツを名前付きのyieldブロックとしてレイアウトに挿入できます。

すなわち

``` app/views/hoge/show.html.slim
- content_for :title, 'hogehoge'
```

とすることで、以下のlayout側の`yield（:title）`に`hogehgoe`が入ります。

``` app/view/layouts/application.html.slim
html
  head
    title 　= yield(:title) # <- 'hogehoge'　が入る
  body
    yield
```

また、この`content_for`は同じ名前を持つものに対し複数回適用することが可能であり、その結果は単純に繋げられていきます。すなわち、

```app/views/hoge/show.html.slim
- content_for :title, 'hogehoge'
- content_for :title, ', ほげほげ'
```

とすると、layout側の`yield（:title）`には、`hogehoge`と`, ほげほげ`を繋げた結果`hogehoge, ほげほげ`が入ります。

``` app/view/layouts/application.html.slim
html
  head
    title = yield(:title) # <- 'hogehoge, ほげほげ'　が入る
  body
    yield
```

ただ、`content_for`には問題があります。`content_for`利用時は全ての`content_for`を見る必要があるため、template全てを読み込む必要があります。結果、通常時のviewの処理順序と変わりがなく、Streamingの恩恵を受けられません。では、どうすればこの問題を解決できるのでしょうか？ここで、`provide`の出番です。

## ストリーミング時の`provide`の挙動
通常時は`content_for`と変わりません。ですが、Streaming時は一番最初の呼び出しで名前付きのコンテンツを決定してしまいます。そのため、`content_for`と同じように`provide`を２回呼び出すと以下のようになります。

```app/views/hoge/show.html.slim
- provide :title, 'hogehoge'
- provide :title, ', ほげほげ'
```

``` app/view/layouts/application.html.slim
html
  head
    title = yield(:title) # <- 'hogehoge'　が入る
  body
    yield
```

このようにすることで、早い段階でlayout内のheadのtitleの内容が定まるので、ヘッダー部分をより早くクライアントに送信できるようになり、Streamingの恩恵を受けられるようになります。

# 結局、`content_for`と`provide`のどっちを使えばよいのか？
以上のことから結論付けると、基本は`content_for`を使うのが良いかと思います。逆に、(複数回呼び出される特殊な事がない限り)Streaming時は`provide`を使うようにしないといけません。

ざっと調べて書いたので何か間違いや分かりにくい所、誤字脱字等あれば、コメント頂ければと思います。

## 参考
http://api.rubyonrails.org/v5.1/classes/ActionController/Streaming.html
https://stackoverflow.com/questions/27814500/ruby-on-rails-provide-vs-content-for
http://railscasts.com/episodes/266-http-streaming?language=ja&view=asciicast