💁
Rails 部分テンプレートで defined? を避けて、local_assigns や locals マジックコメントを使おう
はじめに
Ruby on Rails には、部分テンプレート(パーシャル)という機能があり、View を分割して再利用できます。
その部分テンプレートで、以下のようなコードを見たことはありませんか?
<div>
<h1>
<% if defined?(title) %>
<%= title %>
<% else %>
未設定
<% end %>
</h1>
<% if defined?(body) %>
<p><%= body %></p>
<% end %>
<% if defined?(comments) && comments.present? %>
<ul>
<% comments.each do |comment| %>
<li><%= comment %></li>
<% end %>
</ul>
<% end %>
</div>
このように defined? を使って、部分テンプレートに変数が渡されたかどうかで表示を切り替える方法があります。
私自身、昔から Rails を使っていてよく見かけていたので、とくに気にしていませんでした。
ただし、この方法は現状ではあまり良い方法とは言えません。
では、なぜ良くないのか、どのように改善できるのかを考えてみます。
defined? がなぜ良くないのか
defined? を使うことで、変数が存在するかどうかを簡単に判定できますが、いくつか問題点があります。
- コードの可読性や保守性が下がる
- 意図せず変数が生成されて挙動が変わる
- タイプミスした変数名でも
defined?だと false になり、意図しない動作になる
改善方法
local_assigns を使う
Rails の部分テンプレートでは、local_assigns というハッシュでローカル変数の有無を判定できます。
<div>
<h1>
<%= local_assigns[:title] || "未設定" %>
</h1>
<% if local_assigns[:body].present? %>
<p><%= body %></p>
<% end %>
<% if local_assigns[:comments].present? %>
<ul>
<% local_assigns[:comments].each do |comment| %>
<li><%= comment %></li>
<% end %>
</ul>
<% end %>
</div>
この方法なら、親ビューから明示的に渡された変数だけを判定できます。
参考
- https://guides.rubyonrails.org/action_view_overview.html#using-local-assigns
- https://railsguides.jp/action_view_overview.html#local-assignsを使う
locals マジックコメントを使う
Rails 7.1 以降では、部分テンプレートの先頭に # locals: コメントを書くことで、必要なローカル変数を明示できます。
<%# locals: (title:, body:, comments: []) %>
<div>
<h1><%= title || "未設定" %></h1>
<% if body.present? %>
<p><%= body %></p>
<% end %>
<% if comments.present? %>
<ul>
<% comments.each do |comment| %>
<li><%= comment %></li>
<% end %>
</ul>
<% end %>
</div>
この方法を使うと、変数が渡されていない場合にエラーとなり、意図しない挙動を防げます。
参考
- https://guides.rubyonrails.org/action_view_overview.html#strict-locals
- https://railsguides.jp/action_view_overview.html#厳密なlocals
まとめ
部分テンプレートで変数の有無を判定する際は、defined? ではなく local_assigns や locals マジックコメントを使うことで、より安全で可読性の高いコードになります。
最後まで読んでいただきありがとうございます。この記事が少しでも役に立ったと思ったら、Like♥ を押していただけると励みになります。
Discussion