Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Ruby on Rails 7.1徹底解説:パーシャルのstrict locals(厳密なローカル変数)を使いこなす

Railsのパーシャル(partial)は長年にわたり活用されてきましたが、裏付けとなるオブジェクト構造を持たないERBスニペットにすぎないため、扱いづらい側面がありました。

近年では、ViewComponentやPhlexといったライブラリが、テンプレートにより意味的な構造を持たせることでビューレイヤーの改善を試みています。これらは優れたライブラリであり、私自身もほぼすべてのプロジェクトでViewComponentを採用しています。とはいえ、素朴なRailsパーシャルが依然として多くのユースケースに適していると感じるのも事実です。

Railsチームは常に機能改善を続けています。今回は、Rails 7.1で導入された新機能「strict locals(厳密なローカル変数)」を詳しく見ていきましょう。

Railsのパーシャルとローカル変数の基本

Railsのパーシャルには任意のローカル変数を渡せます。渡された変数は、まるで魔法のようにパーシャル内で利用可能になります。

<!-- app/views/posts/_card.html.erb -->
<div class="card">
  <%= image_tag("#{icon}.png") %>
  <%= title %>
</div>
<%= render "card", title: "投稿タイトル", icon: "star" %>

しかしこの仕組みは理想的とは言えません。大きなマークアップブロックを含むパーシャルの場合、どの変数を受け入れているのか把握するのが困難だからです。さらに、変数は動的に生成されるため、変数が渡されていない場合はnilが返されるのではなく、エラーが発生します。

例えば、上記パーシャルのiconが任意(オプション)だとしましょう。次のようにiconを省略してレンダリングします。

<%= render "card", title: "投稿タイトル" %>

すると、ActionView::Template::Errorが発生し、「undefined local variable or method 'icon' for #<ActionView::Base:0x00000000023078>」というエラーメッセージが表示されます。

しかも、iconが任意であることはコードを読む人にも分かりません。Rails 7.1以前の回避策は、注入されたすべてのローカル変数を含むlocal_assignsハッシュを確認することでした。

<% if local_assigns[:icon] %>
  <%= image_tag("#{icon}.png") %>
<% end %>

このように書けばicon変数が常にチェックされるため、先ほどのエラーは解消されます。

Rails 7.1では、このようなパーシャルのローカル変数の弱点をstrict localsという概念で解決しようとしています。

Railsにおけるstrict localsとは?

Rails 7.1からは、パーシャルの先頭にマジックコメントを記述することで、受け入れるローカル変数とデフォルト値を定義できるようになりました。

<%# locals: (title:, icon: "default.png") %>

<div class="card">
  <%= image_tag("#{icon}.png") %>
  <%= title %>
</div>

Railsはこのコメントを解析し、定義された変数をパーシャルに設定します。これで、どの変数を受け入れ、どんなデフォルト値が設定されているのかが一目瞭然になります。

呼び出し側から必須のtitle変数を除外してみるとどうなるでしょうか。

<%= render "card", icon: "star" %>

結果としてArgumentErrorが発生し、「missing local: :title」というメッセージが表示されます。以前のエラーに比べて、はるかに分かりやすいメッセージですね!

strict localsを使うべき場面

strict localsは扱いが繊細な面もあるため、常に採用すべきとは限りません。特にレガシーアプリでは慎重さが求められます。

まず、strict localsに最適なユースケースを紹介します。例えば、カード形式でユーザーの詳細を表示するパーシャルがあり、最終サインイン日時は管理者にしか見せたくないケースを考えてみましょう。

<div class="card">
  <p><%= user.name %></p>
  <p><%= user.email %></p>
  <% if show_last_sign_in %>
    <p>最終ログイン: <%= l(user.last_sign_in_at) %></p>
  <% end %>
</div>

このパーシャルは複数の変数と任意の変数を受け取るため、strict localsにぴったりのケースです。次のマジックコメントをファイルの先頭に追加するだけで、可読性が大幅に向上します。

<%# locals: (user:, show_last_sign_in: false) %>

一方、モデルに対応するパーシャル(例:posts/_post.html.erb)の場合、単一のpost変数を受け入れるのはほぼ自明です。こうしたケースでは、strict localsは必ずしも良い選択とは言えません。

strict localsの落とし穴

注意すべき落とし穴がいくつか存在します。だからこそ、strict localsを一律に全面適用することはおすすめしません。

コレクションの暗黙的レンダリング

コレクションを暗黙的にレンダリングする場合:

<%= render @posts %>

Railsはpost変数に加えて、_postパーシャルへ2つの追加変数(post_counterpost_iteration)を注入します。Rails 7.1.2より前は、エラーを防ぐためにこれらを手動で定義する必要がありました。

<%# locals: (post:, post_counter: nil, post_iteration: nil) %>

この問題はRails 7.1.2で修正され、現在ではこれらの変数は任意になっています(とはいえ、必要になったときのために存在を知っておくと良いでしょう)。

ActionCable経由でのブロードキャスト

ActiveRecordのコールバックからActionCable経由でパーシャルをブロードキャストする場合:

class Post < ApplicationRecord
  after_create_commit do
    broadcast_append_to :posts, partial: "posts/post", locals: { post: self }
  end
end

この際、request_idというローカル変数をデフォルトnilで指定する必要があります。これはRailsが内部で使用するためです。

<%# locals: (post:, request_id: nil) %>

これを怠ると、コールバックが実行されたタイミングでエラーが発生します。

以上、駆け足でしたがstrict localsの概観をつかめたかと思います!

導入前に覚えておきたいポイント

既存のパーシャルにstrict localsを追加する作業は、ファイル単位で見れば「全か無か(all-or-nothing)」の取り組みです。発見した変数をマジックコメントへ少しずつ追記していく、という段階的なアプローチは取れません。前述の落とし穴で見たように、マジックコメントに宣言すべき変数をすべて洗い出すのは意外と難しいのです。

とはいえ、strict localsによってコードの意図が格段に読み取りやすくなることを考えれば、新規アプリでは積極的に採用する価値があると言えます(レガシーアプリにも、徐々に遡って適用していくと良いでしょう)。

レガシーアプリでstrict localsを追加する場合は、対象のパーシャルがテストスイートでカバーされていることを必ず確認してください。変数の定義漏れがあると例外が発生するため、デプロイ前にあらゆるユースケースをテストしておく必要があります。

個人的には、コメントに機能を持たせるべきではないと考えているため、マジックコメント方式には賛否もあります。それでも、strict localsはRailsにとって素晴らしい追加機能です。パーシャルへのローカル変数受け渡しは昔から扱いにくい課題でしたが、確実に改善されます。ただし、落とし穴には十分注意してください!

まとめ

本記事では、Rails 7.1のstrict localsについて手短に解説しました。パーシャルとローカル変数の基本的な仕組みを確認したうえで、strict localsを活用すべき場面と、知っておくべき落とし穴を紹介しました。

それでは、Happy coding!

P.S. Ruby Magicの最新記事をいち早く読みたい方は、ぜひRuby Magicニュースレターを購読してください。公開を見逃すことはありません!

Ruby on Rails 7.1徹底解説:パーシャルのstrict locals(厳密なローカル変数)を使いこなす

Ayush Newatia

ゲスト執筆者のAyushは、フリーランスのRuby/Rails開発者です。『The Rails and Hotwire Codex』の著者であり、Bridgetownコアチームの一員でもあります。また、プライバシー重視のメーリングリストアプリ「Scattergun」を運営しています。


  1. 2022年にAppSignalで最も人気のあったRuby記事トップ5

    ```html 年末年始の休暇シーズンが近づき、AppSignalチームはRuby記事の公開を一時お休みし、冬のミニブレイクに入ります。2023年にはさらに多くの記事をお届けできるよう準備を進めています。🍵 ❄️ それまでの間、2022年に最も読まれたRubyブログ記事のトップ5を振り返ってみましょう! Rubyにおけるステートマシン入門 ステートマシン(状態遷移マシン)は、ある対象が取りうるすべての状態と、状態間で許可される遷移を管理する仕組みです。例えばドアのステートマシンなら、「開いている(open)」と「閉じている(closed)」という2つの状態、そして「開く途中(opening

  2. Rubyのメソッド委譲(デリゲーション)をマスターする実践ガイド

    プログラミングにおける「委譲(デリゲーション)」とは、プログラムのある部分から別の部分へタスクを引き渡すことを指します。オブジェクト指向プログラミングにおいて不可欠なテクニックであり、各オブジェクトやメソッドが特定のタスクや振る舞いに責任を持つことで、クリーンで保守しやすいコードを実現します。 委譲を理解し活用することは、Rubyをはじめとするオブジェクト指向言語を習得するうえで重要です。委譲は関心の分離を促進し、コードのモジュール性を高めることで、理解・テスト・リファクタリングがしやすいコードへと導きます。 本記事では、Rubyで委譲を実現する3つの方法——明示的な委譲、Forwardabl