`.in_order_of`に配列値を渡して複数レコードをグループ化してソートする

rails/rails

in_order_of メソッドが配列値を受け付けるようになり、複数の値を同一順位にグループ化したうえで、グループ間の順序を別のクエリで制御できるようになりました。

背景

これまで in_order_of は、各値を独立した優先順位として扱うことしかできませんでした。たとえば :published:canceled を同じ順位グループとして扱い、その後に :archived を続けるような「グループ化ソート」は、既存のメソッドでは直接表現できませんでした。

このPRは「レコードをグループ化してから、グループ間をさらに別の順序で並べたい」というユースケースを動機としています。今回の変更により、in_order_of の値リストに配列を入れ子にするだけでグループ化ソートが実現できます。

技術的な変更

in_order_of の型キャスト処理と CASE 式の生成ロジック、フィルタ用 WHERE 句の3箇所が変更されています。

型キャスト処理では、値が配列かどうかを is_a?(Array) で判定し、配列の場合は各要素を個別にシリアライズするよう変更されました。

変更前:

values = values.map do |value|
  caster.serialize(value) if caster.serializable?(value)
end

変更後:

values = values.map do |value|
  if value.is_a?(Array)
    value.map do |current_value|
      caster.serialize(current_value) if caster.serializable?(current_value)
    end
  else
    caster.serialize(value) if caster.serializable?(value)
  end
end

CASE 式を生成する build_case_for_value_position メソッドでも、値が配列のときは column.eq の代わりに column.in を使った WHEN 節を生成するよう拡張されています。nil を含む配列の場合は column.in(value.compact).or(column.eq(nil)) に変換され、NULL値も正しく扱われます。

フィルタ用の WHERE 句生成では、values.flatten(1) を呼び出して入れ子配列を平坦化してから IN 句を構築します。これにより、グループ化された値もすべてフィルタ対象に含まれます。この変更によって生成されるSQLは次のようになります。

SELECT "posts".* FROM "posts"
WHERE "posts"."state" IN (1, 2, 3)
ORDER BY CASE
  WHEN "posts"."state" IN (1, 2) THEN 1
  WHEN "posts"."state" = 3 THEN 2
END ASC, "posts"."created_at" DESC

設計判断

値ごとの個別シリアライズが採用されました。PR内では Array.wrap を使う案も言及されていますが、パフォーマンス上の懸念から見送られています。また、配列型カラムへの影響(配列全体を型キャストすべきケースの有無)も課題として挙げられており、今後の議論の余地が残されています。

flatten(1) の深さを1に限定しているのも注目点です。深さを1に制限することで、入れ子配列の第1レベルだけを展開し、グループとしての意味を持つ配列構造を CASE 式の生成段階まで保持したまま、WHERE 句の構築時にのみ平坦化する設計になっています。

まとめ

型キャスト・CASE式生成・WHERE句生成の3箇所に最小限の is_a?(Array) 分岐を追加するだけで、既存のスカラー値による動作を維持しながらグループ化ソートを実現しています。in_order_of の値リストに配列を入れ子にするという直感的なAPIで、複雑なカスタムソートを SQLの CASE 式に落とし込めるようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
766d9dc6

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

ファイル名付きシンタックスハイライトやGitHubへのPRリンクが、ガイドライン通りの正しい形式で記述されています。

対象読者への適合性 ✓ PASS

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

`in_order_of`や`CASE`式などの用語を前提としており、専門知識を持つエンジニア向けに適切なレベルで書かれています。

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

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

各セクションが総論・各論の構成を持ち、各段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、可読性が高いです。

Diff内容との照合 ✓ PASS

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

記事中のコードスニペットやSQL例は、提供されたDiffやPR Descriptionの内容を正確に反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`型キャスト`, `シリアライズ`, `flatten(1)`といった技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

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

`is_a?(Array)`による分岐、`column.in`の使用、`nil`値の扱いなど、コード変更に関する技術的な説明がDiffの内容と一致しており、正確です。

事実の突合 ✓ PASS

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

記事内の主張はすべてPRのTitle, Description, Diffから裏付け可能であり、ハルシネーションは検出されませんでした。

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

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

PR番号(#52871)やメソッド名、ファイルパスなどの固有名詞が正確に記載されています。

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

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

記事のタイトルはPRの主題「Allow to pass array values to `.in_order_of`」を的確に要約し、読者に内容を正しく伝えています。

外部知識の正確性 ✓ PASS

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

バージョン情報やリリース予定など、PR情報にない外部知識を持ち込むことなく、提供された情報源の範囲内で記述されています。

時間表現の正確性 ✓ PASS

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

「これまで」「今回の変更により」といった時間軸を示す表現が、PRの文脈と正しく対応しています。