フォーマット指定なしテンプレートを持つコンポーネントを継承すると `NoMethodError` が発生するバグを修正

viewcomponent/view_component

フォーマット指定なしのテンプレート(.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::TemplateDetailsformatnil になることでした。子コンポーネントがテンプレートを継承・解決する際にこの nil 値を参照し、フォーマットを文字列に変換しようとした時点でクラッシュしていました。

技術的な変更

修正は lib/view_component/template.rbTemplate::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.formatnil の場合、DEFAULT_FORMAT:html)を使って ActionView::TemplateDetails を再生成します。既存の localehandlervariant は引き継がれ、フォーマットのみが補完されます。また 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 テンプレートを持つコードベースとの互換性を保ちながらバグを修正した変更です。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
5bec8003

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術変更・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の構成が記事全体で明確に適用されており、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:path/to/file.rb)やGitHubのIssue/PRへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

ViewComponentの内部実装に関する内容であり、専門知識を持つエンジニアという対象読者に適した技術レベルと表現で書かれています。

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

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

各セクションが総論→各論で構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されており、非常に読みやすいです。

Diff内容との照合 ⚠ WARNING

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

変更後のコード引用は正確ですが、「変更前」として示されたコードブロックはDiffに存在しません。これは変更点を分かりやすく示すための表現であり許容範囲ですが、厳密にはDiffと一致しません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`ActionView::TemplateDetails`, `Kernel.warn`, `render_inline`など、技術用語が正確かつ適切な文脈で使用されています。

説明の技術的正確性 ✓ PASS

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

「`details.format`が`nil`の場合にデフォルト値を補完する」という説明は、Diffで追加されたコードロジックと完全に一致しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張(問題の背景、修正内容、テストの追加など)は、PRのDescription、Diff、関連Issueの内容によって裏付けられており、ハルシネーションは見られません。

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

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

PR番号(#2593)とIssue番号(#2573)が正確に記載・リンクされています。

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

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

記事のタイトルは、元のPRタイトル「Fix bug where inheritance of components with formatless templates improperly raised a NoMethodError.」の内容を正確に反映しています。

外部知識の正確性 ✓ PASS

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

記事には、バージョンのサポート状況やリリース日程など、PR情報に基づかない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

「発生していました」「報告されていました」など、過去に起きていた問題に対する修正であることが時間表現から正しく伝わります。