Action TextのBlobアタッチメントがMarkdownリンクを生成
Action Textの to_markdown メソッドが、レンダリングコンテキストが利用可能な場合にActiveStorage::BlobアタッチメントをMarkdownリンクとして出力するようになりました。これにより、コンテンツのMarkdown変換時に添付ファイルへの実際のリンクが含まれるようになります。
背景
#56858 でAction TextにMarkdown変換機能が追加されましたが、ActiveStorage::Blob#attachable_markdown_representation は単なるプレースホルダー [caption] を返すだけで、実際のリンクを生成していませんでした。一方、RemoteImage は  の形式で正しいMarkdownリンクを生成していたため、実装に一貫性がありませんでした。
この非対称性により、同じAction Textコンテンツ内でも、リモート画像は実用的なMarkdownリンクとして変換される一方、ActiveStorageのBlobアタッチメントはプレースホルダーのままという状況が生じていました。#56894 がこの問題を解消しています。
技術的な変更
Markdown変換時にBlobアタッチメントをリンクとして出力するため、to_markdown メソッドに attachment_links パラメータが追加されました。このパラメータを true に設定すると、レンダリングコンテキストが利用可能な場合にBlobアタッチメントがMarkdownリンクとして変換されます。
メソッドシグネチャの変更
RichText#to_markdown、Content#to_markdown、Attachment#to_markdown の各メソッドに attachment_links パラメータが追加されました:
def to_markdown(attachment_links: false)
body&.to_markdown(attachment_links: attachment_links).to_s
end
このパラメータはメソッドチェーン全体を通じて伝播し、最終的に各アタッチメントの attachable_markdown_representation メソッドに渡されます。
Blobのリンク生成ロジック
ActiveStorage::Blob#attachable_markdown_representation の実装が大きく変更されました:
def attachable_markdown_representation(caption = nil, attachment_links: false)
title = (caption || filename).to_s
if attachment_links
renderer = ActionText::Content.renderer
raise ArgumentError, "attachment_links requires a rendering context" unless renderer
url = renderer.url_for(self)
if image?
"!#{MarkdownConversion.markdown_link(title, url)}"
else
MarkdownConversion.markdown_link(title, url)
end
else
"[#{MarkdownConversion.escape_markdown_text(title)}]"
end
end
attachment_links: true の場合、ActionText::Content.renderer からレンダリングコンテキストを取得し、url_for(blob) を通じてBlobのURLを生成します。画像Blobは  形式、非画像Blobは [title](url) 形式で出力されます。レンダリングコンテキストが利用できない場合(例:Railsコンソール)は ArgumentError が発生します。
既存の動作との互換性
attachment_links パラメータのデフォルト値は false であり、既存のコードは従来通りプレースホルダー形式 [title] で変換されます:
# デフォルト動作(変更なし)
message.content.to_markdown
# => "Check out this [report.pdf]"
# リンク生成を有効化
message.content.to_markdown(attachment_links: true)
# => "Check out this [report.pdf](http://example.com/rails/active_storage/blobs/...)"
カスタムアタッチメントへの影響
attachable_markdown_representation メソッドのシグネチャが変更されたため、このメソッドをオーバーライドしているカスタムアタッチメントは新しいパラメータに対応する必要があります。ただし、キーワード引数であるため、パラメータを使用しない実装でも動作は継続します:
# 新しいシグネチャに対応
def attachable_markdown_representation(caption, attachment_links: false)
"[@#{name}](#{profile_url})"
end
ContentAttachment、MissingAttachable、RemoteImage の各クラスも新しいシグネチャに更新されています。
設計判断
URL生成はグローバルな default_url_options ではなく、スレッドローカルの ActionText::Content.renderer を通じて現在のリクエストコンテキストから導出されます。
この設計により、host、protocol、script_name といったURL構成要素がリクエストから自動的に取得されます。PR本文では、リクエストごとに script_name が変わるマルチテナントアプリケーションにおいて重要だと説明されています。
renderer の設定は、エンジンイニシャライザ内で around_action により行われます。コントローラーやメーラーのアクション内では常に適切なレンダリングコンテキストが利用可能ですが、Railsコンソールのような環境では利用できません。この制約により、リンク生成が明示的に要求されたにもかかわらず実行できない状況では、エラーを発生させる挙動となっています。
まとめ
本PRは、Action TextのMarkdown変換機能を実用的なものにするための拡張です。attachment_links パラメータの追加により、リクエストコンテキストが利用可能な状況ではBlobアタッチメントを実際のMarkdownリンクとして変換できるようになりました。デフォルト動作は変更されていないため、既存のコードへの影響は最小限に抑えられています。