Railsチュートリアルの演習で、パーシャル名によるパーシャルの呼び出しでハマったポイントをまとめます。
環境
・Rails 5.1.6
・Rails チュートリアル第4版(Rails5.1)
演習問題
演習13.3.2.1
Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。
演習前時点でのHomeビューは以下の通り。
<% 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
から呼び出す。
<% 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ビューを再描画します。
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
内のパーシャルを探します。そのため、この場合はパーシャル名のみでパーシャルを呼び出していても、エラーが起きていませんでした。
正しい解答
<% if logged_in? %>
<%= render 'static_pages/logged_in' %>
<% else %>
<%= render 'static_pages/not_logged_in' %>
<% end %>
views/
からの相対パスを指定すると、期待通りのパーシャル(logged_in
)を呼び出すことができ、エラーが出なくなりました。
まとめ
ビューから、パーシャル名のみでパーシャルを呼び出すときは、そのビューを描画するコントローラを考慮する必要がある。