複数のIDの配列でwhereしたあと、そのIDの配列の順番の通りにorderしたいという場合がたまーにあります。2年に1回ぐらいあります。

実際に必要になったときの機能要件はうまく伝えられないんですが、こんな感じの意味です。

ids = [3, 1, 5]
items = Item.where(id: ids).order("3,1,5の順に並べたい...")

自力で頑張るとしたらSQLでCASE式でインデックスにマッピングさせるようにして、3のときは0、1のときは1、5のときは2のようにして、マッピング結果をORDERする感じになるかと思います。

それをgem化してくれてるのがこれ。

panorama-ed/order_as_specified
Add arbitrary ordering to ActiveRecord queries. Contribute to panorama-ed/order_as_specified development by creating an account on GitHub.

SQL組み立て部分を抜粋するとこんな感じ。CASE式でSQLを組み立てています。

when_queries = conditions.map.with_index do |cond, index|
  "WHEN #{cond} THEN #{index}"
end
case_query = "CASE #{when_queries.join(' ')} ELSE #{conditions.size} END"
scope = order(Arel.sql("#{case_query} ASC"))

というわけで、order_as_specifiedを使えば最初の要件を満たせます。

最初の例は

ids = [3, 1, 5]
items = Item.where(id: ids).order_as_specified(id: ids)

と書けます。

もちろんIDだけじゃなくて、ステータスごとに並び替えるみたいなことにも使えます。

条件が増えると動的に組み立てられるSQL分のCASEの分岐が増えるので、順番に並べたいIDなどがあまりに多いときには使わないほうがよさそうでした。うろ覚えですが、数百件ぐらいでも遅くなったような。。

2年後ぐらいにRailsしてたらまた同じようなことを調べると思うので、自分のために残しておこう。