Herb gem対応: バンドルERBテンプレートのHTML5違反を修正

rails/rails

Rails組み込みのERBテンプレートに潜在していたHTML5仕様違反と2013年来のレンダリングバグを修正しました。これにより、HTML-aware ERBコンパイラである Herb gem(0.10+)を採用したアプリケーションが、Railsのバンドルテンプレートを読み込む際に発生していた起動エラーを解消します。

背景

Herb 0.10は、コンパイル時にHTML5ネスト検証とERB属性位置検証を行うstrict modeを導入しました。この検証器はERBテンプレートのコンパイル時に Herb::Engine::InvalidNestingErrorHerb::Engine::SecurityError を発生させます。Rails 8.1.xでHerb 0.10を採用したアプリが起動できない問題として表面化しました。

すでにコミット 84023dfdrescues/routing_error.html.erb<p> 内に <h2>/<ol> を配置するHTML5違反を修正していましたが、さらに3件の違反と1件の潜在的なレンダリングバグが残存していました。Herbのリポジトリには、バンドルテンプレートをバリデーション対象から除外するper-pathスコープ設定機能を求めるIssue(marcoroth/herb#1744)も上がっていましたが、Rails側で無効なマークアップを修正するのが本質的な解決策と判断されました。

本PRは、リポジトリ内の全バンドルERBテンプレート66ファイル(actionpack/actionmailbox/actiontext/railties/guides/tools/tasks/)を herb compile(herb 0.10.1)で検証した結果を踏まえており、修正前は3件の失敗(MissingOpeningTag 1件、SecurityError 2件)があったものを0件にしています。

技術的な変更

本PRは4ファイルにわたる修正で、問題のカテゴリは「孤立タグ」と「ERB出力式を属性名スロットに配置するパターン」の2種類に分類できます。

2013年来のレンダリングバグ: 孤立した </code> タグの除去

routing_error.text.erb には、2013年のコミット a725a453 でテキストテンプレートが作成された際から、対応する開きタグのない </code> が含まれていました。この閉じタグは、XHRやテキストエラーレスポンスに文字列 </code> としてそのままレンダリングされ続けていたものです。

変更前:

  - <%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %>

変更後:

  - <%= route.inspect.delete('\\') %> failed because <%= reason.downcase %>

HerbのHTML-aware parserがこの孤立タグを MissingOpeningTag として検出したことで、10年以上見逃されてきたバグが発見されました。

ERB出力式の属性名スロット配置を制御フロー式へ変換

email.html.erbguides/source/layout.html.erb では、selected 属性を <%= cond ? 'selected' : '' %> のようにERB出力式(<%= %>)で属性名スロットに動的に埋め込むパターンが使われていました。Herbはこれを SecurityError(属性名位置にERB出力式)として拒否します。制御フロー式(<% if %>)を使った静的テキスト出力に変換することで解消しています。

変更前(email.html.erb):

<option <%= request.format == Mime[:html] ? 'selected' : '' %> value="<%= part_query('text/html') %>">View as HTML email</option>
<option <%= request.format == Mime[:text] ? 'selected' : '' %> value="<%= part_query('text/plain') %>">View as plain-text email</option>

変更後(email.html.erb):

<option<% if request.format == Mime[:html] %> selected<% end %> value="<%= part_query('text/html') %>">View as HTML email</option>
<option<% if request.format == Mime[:text] %> selected<% end %> value="<%= part_query('text/plain') %>">View as plain-text email</option>

変更前(layout.html.erb):

<option value="https://edgeguides.rubyonrails.org/"<%= " selected" if @edge %>>Edge</option>
<option value="https://guides.rubyonrails.org/v<%= version %>/<%= @path %>"<%= " selected" if @version&.start_with?("v#{version}") %>><%= version %></option>

変更後(layout.html.erb):

<option value="https://edgeguides.rubyonrails.org/"<% if @edge %> selected<% end %>>Edge</option>
<option value="https://guides.rubyonrails.org/v<%= version %>/<%= @path %>"<% if @version&.start_with?("v#{version}") %> selected<% end %>><%= version %></option>

この変換には副作用として、非選択時に <%= '' %> が出力していた二重スペース(<option value=...>)が解消される効果もあります。railties/test/application/mailer_previews_test.rb 内の6箇所のアサーション '<option value="..."'(二重スペース)が '<option value="..."'(シングルスペース)に更新されています。

設計判断

制御フロー式への変換アプローチ が採用されました。レンダリング結果は意味的に同一であり、選択時の出力(<option selected value="...">)は変わりません。

HerbがERB出力式を属性名スロットへの配置を SecurityError とする設計は、HTML5仕様の観点から正当です。属性名は静的なトークンであり、動的コンテンツを属性名として展開すると属性インジェクション脆弱性の温床になりえます。一方で、<% if %> selected<% end %> 構文は属性の「値」ではなく「存在そのもの」を制御する表現として、HTML5のboolean属性(selecteddisabledchecked など)の意味論とも自然に整合します。

下流への回避策(Herbのper-pathスコープ設定)ではなくRails本体のテンプレートを修正する判断は、根本的な原因への対処として適切といえます。

まとめ

本PRはHerb 0.10との互換性確保を目的としながら、2013年から存在したレンダリングバグの除去と、HTML5 boolean属性の正しい記述への統一という複合的な改善をもたらしています。ERB出力式を属性名スロットに置くパターンは一見無害に見えますが、HTMLの意味論的な正確性とコンパイラの検証可能性の両面で問題を持つことが改めて示されました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
00d93ec8

この記事は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リンク記法の正確性

ファイル名付きシンタックスハイライト(```erb:filepath```)や、コミットID・PR番号のリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Herb gem、ERBコンパイラ、HTML5仕様など、専門知識を持つエンジニアを対象とした適切な技術レベルと表現で記述されています。

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

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

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られています。段落の長さも適切です。

Diff内容との照合 ✓ PASS

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

記事内のすべてのコードブロックは、提供されたDiffの内容と完全に一致しています。テストコードの変更に関する言及も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「HTML-aware ERB compiler」「ERB出力式」「制御フロー式」などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

「孤立した`</code>`タグ」の問題や「ERB出力式を制御フロー式へ変換」する理由、そしてその副作用(二重スペースの解消)についての説明は、技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張(例:2013年からのバグ、関連Issue番号、検証したファイル数とエラー件数)は、PRのDescriptionやAdditional informationで完全に裏付けられており、ハルシネーションは検出されませんでした。

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

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

PR番号(#57453)、コミットID(短縮形だが正しい)、関連Issue番号、検証ファイル数(66)、エラー件数(3→0)など、すべての数値・固有名詞が正確です。

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

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

記事のタイトル「Herb gem対応: バンドルERBテンプレートのHTML5違反を修正」は、PRの主題である「HTML-aware ERB compilerのエラー修正」を的確に要約しています。

外部知識の正確性 ✓ PASS

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

記事の内容はすべてPR情報に基づいており、PRに記載のない外部知識(LTS、EOLなど)の追加はありません。

時間表現の正確性 ✓ PASS

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

「すでに...修正していました」「2013年来の...」といった時間表現は、PR内の「already fixed」「since ... 2013」と正確に一致しています。