ActiveStorage::FixtureSetで生成されるキーのテナントプレフィックス欠落に対応

basecamp/activerecord-tenanted

ActiveStorage::FixtureSetが生成するBlobキーには、テナントプレフィックスが付与されないため、Tenanted::DiskService#path_forがパス分割に失敗する問題が修正されました。テナントプレフィックスのないキーに対して、標準のDiskServiceの挙動にフォールバックすることで、Fixtureを用いたテストが正常に動作するようになります。

背景

activerecord-tenanted では、テナント分離を実現するために、ActiveStorageのBlobキーにテナントプレフィックスを付与する仕組みが実装されています。通常、キーは tenant_name/blob_key の形式となり、path_for メソッドはこの形式を前提に / で分割してテナントディレクトリとBlobキーを取得していました。

しかし、ActiveStorage::FixtureSet.blob が生成するBlobキーには、このテナントプレフィックスが含まれません。そのため、path_for メソッド内で / による分割を試みると、folder_for メソッドで NoMethodError が発生していました。この問題により、Fixtureを使用したテストケースが実行できない状況が生じていました。

技術的な変更

lib/active_record/tenanted/storage.rbpath_for メソッドに、キーにテナントプレフィックスが含まれているかを判定する条件分岐が追加されました。

変更前:

def path_for(key)
  if ActiveRecord::Tenanted.connection_class
    # TODO: this is brittle if the key isn't tenanted ... errors in folder_for:
    #
    #   NoMethodError undefined method '[]' for nil (NoMethodError) [ key[0..1], key[2..3] ].join("/")
    #
    tenant, key = key.split("/", 2)
    File.join(root, tenant, folder_for(key), key)
  else
    super
  end
end

変更後:

def path_for(key)
  if ActiveRecord::Tenanted.connection_class && key.include?("/")
    tenant, key = key.split("/", 2)
    File.join(root, tenant, folder_for(key), key)
  else
    super
  end
end

key.include?("/") による判定が追加され、テナントプレフィックスを含まないキーの場合は super で親クラス(標準の ActiveStorage::Service::DiskService)の実装にフォールバックするようになりました。これにより、Fixtureで生成されたキーでも正しくパスが解決されます。

テストケースでは、以下のようなFixture定義が追加されています:

Blob Fixture:

one_image_blob: <%= ActiveStorage::FixtureSet.blob filename: "goruco.jpg", service_name: "test" %>

Attachment Fixture:

one_image:
  name: image
  record: one (Note)
  blob: one_image_blob

このFixtureを利用した統合テストが test/integration/test/active_storage_test.rb に追加され、Fixtureが正常に読み込まれることが検証されています。

設計判断

キーの形式による条件分岐 という単純な判定手法が採用されました。

コード内のTODOコメントにあるように、テナントプレフィックスのないキーへの対応は既知の課題でしたが、今回は最小限の変更で問題を解決する方針が取られています。key.include?("/") による判定は、テナントプレフィックスの有無を直接的に判断する方法であり、既存のテナント付きキーの処理には影響を与えません。

より厳密な判定方法(正規表現パターンマッチングや、キーの生成元を追跡する仕組みなど)も考えられますが、Fixtureのキーは通常のランタイムで生成されるキーとは異なる経路で作られるため、シンプルな形式判定で十分に機能します。標準のDiskServiceへのフォールバックにより、Fixtureだけでなく、他の理由でテナントプレフィックスを持たないキーが存在した場合にも柔軟に対応できます。

まとめ

本PRは、テナント分離を実現する path_for の実装に、テナントプレフィックスのないキーへの対応を追加した変更です。キーの形式チェックを追加するだけで、Fixtureベースのテストを可能にしつつ、既存のテナント分離機能との互換性を維持しています。この修正により、テスト環境と本番環境の両方で一貫したActiveStorageの動作が保証されます。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

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

ファイル名付きのシンタックスハイライト(```言語:ファイルパス)と、PR番号のリンク記法([#280](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

内容はActiveStorageとテナント分離の内部実装に関わるもので、専門知識を持つエンジニアという対象読者に適しています。冗長な説明はありません。

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

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

各セクションは総論から各論へ展開され、各段落はトピックセンテンスで始まっています。1段落1トピックの原則と適切な段落長が守られており、高い可読性を確保しています。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロック(`lib/active_record/tenanted/storage.rb`の変更、Fixtureファイルの追加)は、提供されたDiff情報と完全に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`ActiveStorage::FixtureSet`、`テナントプレフィックス`、`フォールバック`といった技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

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

「`key.include?("/")`の条件分岐により、テナントプレフィックスのないキーは`super`(標準のDiskService)にフォールバックする」という説明は、コードの変更内容と技術的に完全に一致しています。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescription、Diff内のコード、またはコードコメントによって裏付けられています。「設計判断」セクションはコードから読み取れる妥当な技術的考察であり、ハルシネーションは検出されませんでした。

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

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

PR番号(#280)やその他の固有名詞が正確に記載されています。

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

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

記事のタイトル「ActiveStorage::FixtureSetで生成されるキーのテナントプレフィックス欠落に対応」は、PRのタイトル「Fix `ActiveStorage::FixtureSet` support」の内容を的確かつ具体的に表現しています。

外部知識の正確性 ✓ PASS

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

記事には、バージョン情報やリリース予定など、PR情報に基づかない外部知識の捏造は含まれていません。

時間表現の正確性 ✓ PASS

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

「〜修正されました」といった過去形の表現が使われており、PRの変更内容を反映する時間表現として正確です。