FormBuilder#to_partial_path が Builder 以外のサブクラスで nil を返す不具合を修正

rails/rails

ActionView::Helpers::FormBuilder._to_partial_path が、クラス名に Builder サフィックスが無いサブクラスで nil を返していたバグを、文字列置換メソッドの変更により解消しました。これにより、カスタムビルダーの名前付け規則に依存せず正しいパーシャルパスが生成されます。

背景

FormBuilder#to_partial_path はビルダーオブジェクトをテンプレートに渡す際に使用される内部メソッドで、クラス名からパーシャル名を導出します。元の実装は String#sub! を用いて "_builder" サフィックスを除去していましたが、パターンがマッチしない場合は nil が返り、||= によるキャッシュが nil になるため以降の呼び出しでも常に nil が返っていました。この挙動は Ruby の仕様に起因する典型的なミスリーディングで、AdminForm のように Builder で終わらないサブクラスでパーシャルが見つからずレンダリングが失敗していました。

テストスイートにも LabelledFormBuilderSubclass というサブクラスが含まれ、同様に nil が返っていることが確認されていました。従来は *Builder という命名規則が事実上の前提となっていたため問題が表面化しにくく、実装上のバグとして長年残存していました。今回の PR はこの隠れた不具合を顕在化し、正しいパーシャルパスを返すように修正しています。

技術的な変更

FormBuilder のクラスメソッド _to_partial_path で使用していた String#sub!String#sub に置き換えました。sub! は置換が行われなかった場合に nil を返すのに対し、sub は元文字列をそのまま返すため、キャッシュ変数 @_to_partial_path が常に文字列を保持します。変更前後の差分は次のとおりです:

@@ -1706,7 +1706,7 @@ def multipart=(multipart)
       end

       def self._to_partial_path
-        @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
+        @_to_partial_path ||= name.demodulize.underscore.sub(/_builder$/, "")
       end

       def to_partial_path

この修正により、以下のように期待通りのパーシャルパスが取得できます。ActionView::Helpers::FormBuilder._to_partial_path は "form"、LabelledFormBuilder._to_partial_path は "labelled_form" のままです。LabelledFormBuilderSubclass._to_partial_path は "labelled_form_builder_subclass"、AdminForm._to_partial_path は "admin_form" と正しく変換されます。従来のケースは影響を受けず、サフィックスが存在すれば除去されます。

設計判断

今回の変更は 最小限の差分 で問題を解決することを優先しています。sub! から sub への置換は 1 行の変更で済み、既存のキャッシュロジックや API 署名を変更せずに済むため、後方互換性が保たれます。また、@_to_partial_path が常に文字列になることで nil がキャッシュされるリスクが排除され、余計な再計算も防げます。

キャッシュは ||= 演算子で遅延評価されますが、sub が常に文字列を返すため一度計算された文字列が永続的に保存されます。これにより、ビルダークラスが多数存在する大規模アプリでも余計な文字列生成が抑制され、パフォーマンスに悪影響を与えることはありません。設計上のトレードオフはなく、コード可読性と安全性が向上した点が主な利点です。

まとめ

FormBuilder#to_partial_pathBuilder で終わらないサブクラスでも正しいパーシャル名を返すようになり、カスタムビルダーの命名に制約がなくなりました。シンプルな文字列置換メソッドの差し替えだけで、長年潜在していたバグとキャッシュの不整合が解消された点が本変更の核心です。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
b5100622

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文がタイトル直下にあり、背景・技術的変更・設計判断・まとめの各セクションが揃っているため、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックは正しく`diff`でハイライトされているが、ファイル名付きハイライトは不要で使用されていない。PRリンクの記法が[PR #57469](...)となっており、推奨の[#57469](URL)形式から外れているため警告としました。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

対象はRailsコア開発者や高度なRubyエンジニアであり、前提知識を前提とした記述になっているため適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論パラグラフで要旨を示し、続く段落で具体的事実・コードを示し、最後に結論パラグラフでまとめている。トピックセンテンスが冒頭にあり、段落は1トピック・6文未満で空行で区切られている。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のdiffブロックは提供されたDiffの`action_view/lib/action_view/helpers/form_helper.rb`の変更と完全に一致しており、コード引用に誤りはない。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`sub!`、`sub`、`cache`、`partial path`などの用語はPRで使われている通り正確に使用されている。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

変更理由や動作への影響がPRの説明と合致しており、技術的に正確な記述となっている。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の全ての主張はPRタイトル・Description・Diffで裏付けられており、根拠のない推測や外部知識は含まれていない。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号#57469、ファイルパス、行番号などの数値・固有名詞は正確に記載されている。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルはPRの内容「FormBuilder#to_partial_path が Builder 以外のサブクラスで nil を返す不具合を修正」と一致している。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

バージョンサポートやリリース日程など、PRに記載されていない外部知識は一切含まれていない。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現は使用されておらず、PRの記述と矛盾する箇所もない。