ActionText: ブロックをエディタ要素のDOM子要素として描画するよう変更

rails/rails

ActionTextのブロック引数が持つセマンティクスが変更され、ブロックはエディタ要素のDOM子要素として描画されるようになりました。これにより、value(リッチテキストの内容)とブロック(カスタム要素の埋め込み)が独立して扱えるようになります。

背景

#55827 でActionTextのタグヘルパーにブロック引数のサポートが導入されましたが、その実装はブロックを value の代替として扱うものでした。つまり value が未指定のときにブロックの内容を初期テキストとして使用する「value OR block」の設計であり、両者を同時に利用することはできませんでした。

この制約は、LexxyのようなActionText互換のエディタとの統合において問題になります。Lexxyでは、ブロックをリッチテキストの初期値ではなく、エディタ要素へのカスタム子要素(プロンプトメニュー等)の注入に使用します。この変更は、Lexxyのような他のエディタとの統合を公式にサポートするものです。

技術的な変更

ブロックの扱いがヘルパー層からエディタタグ層へ移動し、value との独立性が確保されました。

rich_textarea_tag の変更(tag_helper.rb):

変更前はヘルパー側でブロックを早期にキャプチャし、value に代入していました。

変更前:

def rich_textarea_tag(name, value = nil, options = {}, &block)
  value = capture(&block) if value.nil? && block_given?
  options = options.symbolize_keys
  # ...
  render RichText.editor.editor_tag(options)
end

変更後:

def rich_textarea_tag(name, value = nil, options = {}, &block)
  options = options.symbolize_keys
  # ...
  render RichText.editor.editor_tag(options, &block)
end

ブロックのキャプチャが削除され、ブロック自体が editor_tag へそのまま渡されます。

Editor::Tag の変更(editor.rb):

Editor::Tag がブロックを保持し、render_incontent_tag に渡すことでDOM子要素として展開します。

class Editor::Tag
  def initialize(editor_name, options = {}, &block)
    @editor_name = editor_name
    @options = options
    @block = block
  end

  def render_in(view_context)
    options[:class] ||= "#{editor_name}-content"
    view_context.content_tag(element_name, nil, options, &@block)
  end
end

Trixの後方互換性(trix_editor.rb):

TrixEditor::Tag#render_in には特別な処理が追加されています。Trixは隠しinputで値を管理するため、value が未指定かつブロックが存在する場合に限り、ブロックをキャプチャして隠しinputの値として使用します。これにより「ブロックを初期値として使う」という既存の動作が維持されます。

def render_in(view_context, ...)
  name = options.delete(:name)
  form = options.delete(:form)
  value = options.delete(:value)
  value = view_context.capture(&@block) if @block && value.nil?

  options[:class] ||= "#{editor_name}-content"
  options[:input] ||= # ...
  input_tag = view_context.hidden_field_tag(name, value, id: options[:input], form: form)

  input_tag + view_context.content_tag(element_name, nil, options)
end

注意点として、Trixは super(親の render_in)を呼ぶのをやめ、content_tag を直接呼び出すようになっています。これはブロックをTrixの子要素として描画させないためです。Trixにおいてブロックは value のフォールバックとしてのみ機能し、DOM子要素にはなりません。

設計判断

エディタの種類によってブロックのセマンティクスを使い分ける設計が採用されました。

カスタムエディタ(Editor::Tag)ではブロックがDOM子要素として展開されます。一方Trixは value が存在しない場合のみブロックを初期値として解釈し、value がある場合は子要素への展開も行いません。この非対称な動作は意図的なもので、Trixの動作における後方互換性を保ちながら、新しいエディタに対しては柔軟な子要素注入の手段を提供します。

この設計では value とブロックの関係がエディタの実装クラスに委ねられており、将来の新しいエディタが独自のセマンティクスを定義できる拡張性を持ちます。

まとめ

ブロックの役割をDOM子要素の注入へと再定義することで、value(リッチテキスト内容)とブロック(UI拡張要素)が独立した関心事として扱えるようになりました。Trixの後方互換性を保ちつつ、Lexxyのようなアーキテクチャを公式にサポートする設計拡張点として機能します。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
f3af802c

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的な変更、設計判断、まとめが「総論→各論→結論」の構成で明確に配置されており、理想的な記事構成です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト、PR番号のリンク記法など、全てのカスタムMarkdown構文が正しく使用されています。

対象読者への適合性 ✓ PASS

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

ActionTextの内部実装という専門的なトピックを、過不足ないレベル感で解説しており、対象読者であるエンジニアに最適化されています。

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

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

各セクション、各パラグラフが「総論→各論」の構造で書かれ、トピックセンテンスが明確です。非常に読みやすく、要点を把握しやすい構成になっています。

Diff内容との照合 ✓ PASS

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

記事で引用されているコードは、提供されたDiffと完全に一致しており、変更点が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「セマンティクス」「DOM子要素」「後方互換性」など、PRの文脈に沿った技術用語が正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

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

コード変更の理由や、Trixエディタで後方互換性を保つための特殊な実装についての説明が、Diffの内容に基づいており、技術的に正確かつ論理的です。

事実の突合 ✓ PASS

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

記事内のすべての主張(Lexxyとの統合、Trixの後方互換性維持など)は、PRのDescriptionやDiffの内容によって裏付けられており、ハルシネーションは見られません。

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

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

PR番号(#56926、#55827)やその他の固有名詞(Lexxy, Trixなど)はすべて正確に記載されています。

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

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

記事のタイトルはPRの主題「support block children in editor elements alongside value」を的確に要約しており、内容との整合性が取れています。

外部知識の正確性 ✓ PASS

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

記事内で言及されている外部情報(例: Lexxy)は、すべてPRのDescriptionに記載されているものであり、PRにない外部知識の追加はありません。

時間表現の正確性 ✓ PASS

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

「導入されましたが」「変更され...なりました」といった時間表現は、PRの文脈と一致しており、変更の前後関係を正しく伝えています。