[Rails]パーシャルをパーシャル名のみで呼び出そうとしてハマったところ

[Railsチュートリアル](https://railstutorial.jp/)の演習で、パーシャル名によるパーシャルの呼び出しでハマったポイントをまとめます。

## 環境
・Rails 5.1.6
・Rails チュートリアル第4版(Rails5.1)

## 演習問題
### 演習13.3.2.1
**Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。**

演習前時点でのHomeビューは以下の通り。

```erb:/sample_app/app/views/static_pages/home.html.erb
<% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
</div>
<% else %>
  <div class="center jumbotron">
  <h1>Welcome to the Sample App</h1>

  <h2>
    This is the home page for the
    <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
    sample application.
  </h2>

  <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>

<%= link_to image_tag("rails.png", alt: "Rails logo"),
            'http://rubyonrails.org/' %>
<% end %>
```

上記の`if`と`else`の中身をそれぞれパーシャルに切り出し、`render`で呼び出すことによって、`if`と`else`の中身を一行でスッキリ書こうという問題ですね。

## 自分の解答
`/sample_app/app/views/static_pages/`内に二つのパーシャル`logged_in`と`not_logged_in`を切り出し、`home.html.erb`から呼び出す。

```erb:/sample_app/app/views/static_pages/home.html.erb
<% if logged_in? %>
  <%= render 'logged_in' %>
<% else %>
  <%= render 'not_logged_in' %>
<% end %>
```

パーシャルを`home.html.erb`と同じフォルダに配置したため、パーシャル名のみで指定できると考え、上のように記述しました。このときのフォルダ構成は以下の通り。

```
views/
　└ static_pages/
    ├ _logged_in.html.erb
    ├ _not_logged_in.html.erb
    └ home.html.erb
```
一見これで問題なく動作していたのですが、homeから空白のマイクロポストを投稿した際に下記のようなエラーが発生しました。

```
ActionView::Template::Error (Missing partial microposts/_logged_in
```
`views/microposts/`内に`_logged_in`パーシャルが無いと怒られています。
`views/static_pages/`内の`_logged_in`パーシャルを探して欲しいのですが...

## 原因
**コントローラからrenderでビューを描画したとき、パーシャル名のみで指定できるパーシャルは、コントローラと対応するビューフォルダ(views/コントローラ名)内のパーシャルである。**

今回の場合、homeから投稿したmicropostが不正(空白または141文字以上)のとき、エラーメッセージを付け加えてhomeビューを再描画します。

```ruby:/sample_app/app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'     # micropostが不正な場合、homeを再描画
    end
  end

  def destroy
  end

  private
    def micropost_params
      params.require(:micropost).permit(:content)
    end
end
```
このように、マイクロポストの作成に失敗したときは、`microposts_controller.rb`から、`render 'static_pages/home'`によって、homeビューを再描画しています。このとき、パーシャル名のみで指定できるパーシャルは`views/microposts`内のパーシャルです。そのため、パーシャル名のみでパーシャルを指定すると、`views/microposts`内を探してしまい、エラーとなります。

```
views/
　├ microposts/      ⬅️ ここを探してしまう！
　└ static_pages/
    ├ _logged_in.html.erb
    ├ _not_logged_in.html.erb
    └ home.html.erb
```

逆に、マイクロポストの作成に成功した場合や、直接homeのページのURLを入力した場合などは、`static_pages_controller.rb`からhomeビューを描画するので、`views/static_pages`内のパーシャルを探します。そのため、この場合はパーシャル名のみでパーシャルを呼び出していても、エラーが起きていませんでした。

## 正しい解答
```erb:/sample_app/app/views/static_pages/home.html.erb
<% if logged_in? %>
  <%= render 'static_pages/logged_in' %>
<% else %>
  <%= render 'static_pages/not_logged_in' %>
<% end %>
```
`views/`からの相対パスを指定すると、期待通りのパーシャル(`logged_in`)を呼び出すことができ、エラーが出なくなりました。

## まとめ
ビューから、パーシャル名のみでパーシャルを呼び出すときは、そのビューを描画するコントローラを考慮する必要がある。