フォーマット指定なしテンプレートを持つコンポーネントを継承すると `NoMethodError` が発生するバグを修正
フォーマット指定なしのテンプレート(.html.erb ではなく .erb)を持つコンポーネントを継承すると、子コンポーネントのコンパイル時に undefined method 'upcase' for nil が発生していました。Template::File の初期化処理でフォーマットが nil の場合にデフォルト値を補完するよう修正し、この継承バグを解消します。
背景
.erb や .slim のようなフォーマット指定なしのテンプレートを使う親コンポーネントを継承すると、子コンポーネントのレンダリングが失敗するという問題が報告されていました(#2573)。
親コンポーネント単体では正常に動作するにもかかわらず、継承した子コンポーネントを render_inline しようとすると undefined method 'upcase' for nil というエラーが発生していました。エラーメッセージから、nil に対して upcase を呼び出しており、フォーマット情報が欠落していることが示唆されていました。
問題の本質は、テンプレートファイルにフォーマットが含まれない場合(例: .erb)、ActionView::TemplateDetails の format が nil になることでした。子コンポーネントがテンプレートを継承・解決する際にこの nil 値を参照し、フォーマットを文字列に変換しようとした時点でクラッシュしていました。
技術的な変更
修正は lib/view_component/template.rb の Template::File クラスの初期化処理に集中しています。フォーマットが nil の場合に :html をデフォルト値として補完するガード節を追加しました。
変更前:
class File < Template
def initialize(component:, details:, path:)
@strip_annotation_line = false
# ...
end
end
変更後:
class File < Template
def initialize(component:, details:, path:)
# If the template file has no format (e.g. .erb instead of .html.erb),
# assume the default format (html).
if details.format.nil?
Kernel.warn("WARNING: Template format for #{path} is missing, defaulting to :html.")
details = ActionView::TemplateDetails.new(details.locale, details.handler, DEFAULT_FORMAT, details.variant)
end
@strip_annotation_line = false
# ...
end
end
details.format が nil の場合、DEFAULT_FORMAT(:html)を使って ActionView::TemplateDetails を再生成します。既存の locale・handler・variant は引き継がれ、フォーマットのみが補完されます。また Kernel.warn でデベロッパーに警告を出力するため、フォーマット指定なしのテンプレートは意図せず使われていた場合でも気づけるようになっています。
テストは FormatLessParentComponent(.erb テンプレート)と FormatLessChildComponent(継承のみ、独自の .erb テンプレートを持つ)という2つのコンポーネントで構成され、test/sandbox/test/rendering_test.rb に再現テストが追加されています。また test/test_helper.rb には "Template format for" を含む警告を raise から除外するガードが追加され、テスト中に意図的に発行される警告がエラーにならないよう配慮されています。
設計判断
フォーマット欠落をエラーではなく警告として扱い、:html にフォールバックする設計が採用されています。
フォーマット指定なしのテンプレート(.erb)は厳密には不正な形式とも言えますが、既存のコードベースに存在している可能性を考慮し、エラーで止めるのではなく動作を継続させつつ Kernel.warn でデベロッパーに通知する方針が取られています。DEFAULT_FORMAT を使った ActionView::TemplateDetails の再生成はイミュータブルなオブジェクトの差し替えであり、既存の details を直接変更しない点も安全です。
なお、この修正はフォーマット指定なしのテンプレートを持つ親コンポーネント「単体」のレンダリングには影響しません。継承時にテンプレートの詳細情報が子コンポーネントへ伝播するパスで初めて nil 参照が露見していたため、修正の対象も Template::File の初期化処理のみに絞られています。
まとめ
本PRは、フォーマット指定なしのテンプレートを持つコンポーネントの継承時に発生していた NoMethodError を、Template::File の初期化処理への単一のガード節追加で解消しています。フォーマット欠落を警告付きで :html にフォールバックさせることで、既存の .erb テンプレートを持つコードベースとの互換性を保ちながらバグを修正した変更です。