今日はRuby on Railsのパーシャルについて2度目の学習になりました。
先月学習した時にも出てきた「パーシャルの中にインスタンス変数をつかってはいけない」という教え。
そういうものか〜と思って従っていましたが、実はあまりちゃんと意味がわかっていなかった様に思います。
今回はきちんとその理由と対処方法についてまとめておこうと思います。
非常に初歩的な基本ですが、バグの原因になる部分と思われるので、しっかり整理して当たり前のことが当たり前にできるようにしていこうと思います。
パーシャルとは
Railsの基本原理の一つである "Don't Repeat Yourself" を実現するための仕組み。
複数のViewで用いられる似たようなコードを「パーシャル」として保存し、同じコードを使う各ファイルでは、毎回同じコードを書かなくても、パーシャルを呼び出す1行を書けば良くなるといった機能である。
なお、パーシャルは「_パーシャル名」という命名がなされます。
これによって、同じコードを何度も書いたりコピペしたりする手間が省けるだけでなく、「パーシャル部分に更新や変更があった場合に、一箇所だけ直せばことたりる」という利点があります。
これは単純に労力を削減しコードがスッキリするだけではなく、たくさん記入しなくて済むため打ち間違いなどのヒューマンエラーによるミスを減らしたり、同じコードを使うファイルが何箇所もあると、その機能の変更や更新があると、全ファイルを直さなければならず、うっかり漏れるとバグになったりすることも防げます。
例えば今回私が作っている掲示板アプリではこんな感じ。
掲示板の入力フォームは新規作成でも編集画面でも共通なので、パーシャル化することになりました。
① パーシャル化前の状態
<div class="d-flex align-items-center">
<h1>掲示板作成</h1>
<div class="ml-auto boards__linkBox">
<%= link_to '一覧', boards_path, class: 'btn btn-outline-dark' %>
</div>
</div>
<%= form_for @board do |f| %>
<div class="form-group">
<%= f.label :name, '名前' %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :title, 'タイトル' %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :body, '本文' %>
<%= f.text_area :body, class: 'form-control', rows: 10 %>
</div>
<%= f.submit '保存', class: 'btn btn-primary' %>
<% end %>
② パーシャルに分割して、パーシャルを呼び出している。
<div class="d-flex align-items-center">
<h1>掲示板作成</h1>
<div class="ml-auto boards__linkBox">
<%= link_to '一覧', boards_path, class: 'btn btn-outline-dark' %>
</div>
</div>
<%= render partial: 'board' %>
③ パーシャルとして移植した掲示板フォーム部分
<%= form_for @board do |f| %>
<div class="form-group">
<%= f.label :name, '名前' %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :title, 'タイトル' %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :body, '本文' %>
<%= f.text_area :body, class: 'form-control', rows: 10 %>
</div>
<%= f.submit '保存', class: 'btn btn-primary' %>
<% end %>
パーシャルを見てみると、確かに分割した段階ではインスタンス変数が使われています。
パーシャル内部でインスタンス変数を使ってはいけない理由
パーシャルはもともとViewの一部でしたから、コントローラからインスタンス変数を渡されて動作するコードが書かれている場合があります(上記①)。
単純にそれをコピペしてパーシャル化すると、Viewからはインスタンス変数が消え、パーシャルに突然インスタンス変数が出てくることになってしまいます。
このことによって、
理由は大きく2つあります。
①パーシャルを使っているviewのなかにはインスタンス変数が出てこない。
=>コントローラでパーシャルで使っていることに気づかず、コントローラ側インスタンス変数を変えたり削除したりするとバグの原因になる。
②パーシャルに直接インスタンス変数を渡す仕組みになっており、コントローラでインスタンス変数をいじるたびに、パーシャルのことも考慮しなければならない。
つまり、インスタンス変数を誤って消したり、勝手に変えてしまったり、いったい何が渡されているかが見えにくかったりするし、あるいは逆に、コントローラ側でインスタンス変数をいじるたびに「そういえばパーシャルで使ってないか確認しないと」といったように手間が増えてしまうということです。
解決策
viewで一度インスタンス変数をローカル変数に格納し、パーシャルではローカル変数を用いるようにすれば、良いです。
上記の例で言えば、以下ようになります。
②のviewでインスタンス変数をローカル変数に入れます。
<%= render partial: 'board', locals: {board: @board} %>
③のパーシャルのインスタンス変数をローカル変数に直します。
<%= form_for board do |f| %>
これだけです。
ちなみにさらに省略した書き方として
<%= render partial: 'board', object: @board %>
<%= form_for object do |f| %>
があります。
これはパーシャル名(ここでいうform)と同名のローカル変数にインスタンス変数を格納してくれるものです。
さらに省略すると、こんな書き方もあります。
<%= render @board %>
これは自動的に_@以下.html.erbを呼び出し、@以下と同名のローカル変数に、インスタンス変数を格納して、渡すところまで自動的に行ってくれるものです。