async_ids が矛盾条件で Promise を返さない不具合を修正

rails/rails

async_ids が常に ActiveRecord::Promise を返すという契約が、矛盾した where 条件 下で配列を返すバグを解消し、非同期クエリの一貫性を確保した。

背景

async_ids は非同期に ID の配列を取得し、結果を ActiveRecord::Promise でラップして返すことが公式に保証されている。しかし、where(id: []) など矛盾した where 句 が存在する場合、内部で ActiveRecord::Result.emptyasync: オプションなしで生成され、then が同期的に実行されて配列が直接返されていた。

この挙動は Promise の契約に反し、Model.where(id: []).async_ids[](Array)を返すという実装上の不整合を引き起こした。結果として、非同期クエリを期待してチェーンした .then 呼び出しが同期実行になるため、呼び出し側で型チェックやエラーハンドリングが機能しないリスクが生じた。

この問題は pluck メソッドでは既に ActiveRecord::Result.empty(async: @async) が使用されており、ids メソッドだけが例外的に非同期情報を失っていたことが根本原因と指摘された。

技術的な変更

activerecord/lib/active_record/relation/calculations.rbids メソッド において、矛盾した where 句を検出した際に生成する空結果を ActiveRecord::Result.empty(async: @async) に変更した。これにより、idspluck が同一の非同期オプション伝搬ロジックを共有することになる。

@@ -396,7 +396,7 @@ def ids
       relation.select_values = columns

       result = if relation.where_clause.contradiction?
-        ActiveRecord::Result.empty
+        ActiveRecord::Result.empty(async: @async)
       else
         skip_query_cache_if_necessary do
           model.with_connection do |c|

テストスイートにも同様の非同期動作を検証するケースが追加された。`activerecord/test/cases/calculations_test.rb` では、矛盾したスコープに対して `async_ids` が期待通り **`ActiveRecord::Promise`** を返すことを `assert_async_equal` で確認している。

```ruby:activerecord/test/cases/calculations_test.rb
@@
     assert_queries_count(0) do
       assert_equal company_ids, Company.where(id: empty_scope_ids).ids
     end
+    assert_async_equal company_ids, Company.where(id: empty_scope_ids).async_ids
   end

## 設計判断

**`ids` メソッド** が `pluck` と同一の空結果生成ロジックを採用することで、非同期クエリ全体の振る舞いを統一したという設計判断が取られた。これにより、個別メソッドごとに `async:` オプションの有無を意識する必要がなくなり、コードベースのシンプルさと保守性が向上した。

新しいキーやラッパーメソッドを導入せず、既存の `@async` フラグを活用した最小限の変更に留めた点は、後方互換性を保ちつつバグ修正を実現した実践的なトレードオフと言える。

## まとめ

`async_ids` が矛盾した where 条件下でも常に **`ActiveRecord::Promise`** を返すようになり、非同期クエリの API 契約が一貫化した。`ids` メソッドに `async:` オプションを付与した空結果生成を追加しただけで、既存コードへの影響はなく、テストも補強された。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
08c4355a

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景(Context)、技術的な変更(Technical Detail)、設計判断(任意)、まとめが明確に構成されており、総論→各論→結論の流れが保たれています。

カスタムMarkdown構文 ✓ PASS

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

コードブロックは `言語:ファイルパス` 形式で正しく記述され、PRリンクは `[#57892](URL)` の形式で正しくハイパーリンク化されています。

対象読者への適合性 ✓ PASS

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

Rails の内部実装に詳しいエンジニア向けの記述で、初心者向けの過度な説明はありません。

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

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

各セクションは総論パラグラフ、各論パラグラフ、結論パラグラフで構成され、段落はトピックセンテンスで始まり 1 トピックに絞られ、長さも適切です。空行で区切られています。

Diff内容との照合 ✓ PASS

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

記事中のコードブロックは提供された Diff と完全に一致しており、ファイル名・変更箇所とも合致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

ActiveRecord::Promise、async_ids、contradiction などの用語が正しく使用され、PR の記述と一致しています。

説明の技術的正確性 ✓ PASS

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

技術的な変更点や影響についての説明は PR 内容と矛盾せず、論理的に正確です。

事実の突合 ✓ PASS

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

記事のすべての主張は PR のタイトル・説明・Diff で裏付けられており、捏造や推測はありません。

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

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

PR 番号 #57892 などの数値は正確です。

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

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

記事タイトルは PR の主旨を正確に日本語で表現しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

Rails のバージョンサポートやリリース日程など、PR に記載されていない外部知識は一切含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PR の記述と一致しています。