ใน Rails ดึง records หลายตัวจาก database ด้วย IDs ง่าย:
Person.where(id: [1, 2, 3]).map(&:id) => [1, 2, 3]
แตถ้าอยากได้ records กลับมาในลำดับ ต่าง ไป? Search engine คืน IDs relevant ก่อน จะ เก็บ records เรียงลำดับนั้นได้ไง?
ลอง where อีกครั้ง:
Person.where(id: [2, 1, 3]).map(&:id) => [1, 2, 3]
ไม่ได้เลย ดังนั้นดึง records กลับมาในลำดับที่ถูกต้องยังไง?
วิธี Relative: case Statements
เหมือน Ruby SQL รองรับ case...when statements
ไม่ค่อยเห็น แต case...when เกือบ ทำหน้าที่เหมือน hashes map ค่าให้ค่า:
case :b when :a then 1 when :b then 2 when :c then 3 end # => 2
Case statement นี้ดูเหมือน hash:
{ :a => 1, :b => 2, :c => 3 }[:b] # => 2
ดังนั้นมีวิธี map keys ไป order ที่ควรปรากฏ และ database สามารถ sort และ return results ตามลำดับ arbitrary นั้น
รู้แล้ว ใส่ IDs และ position ลง case statement ใช้ใน SQL order clause
ถ้าอยาก objects คืนตาม [2, 1, 3] SQL เป็น:
SELECT * FROM people WHERE id IN (1, 2, 3) ORDER BY CASE id WHEN 2 THEN 0 WHEN 1 THEN 1 WHEN 3 THEN 2 ELSE 3 END;
records คืนมาในลำดับถูกต้อง CASE แปลงแต่ละ ID ให้เป็นลำดับที่ควรคืน
แตดู ridiculous และคิด像手建多麻烦
แตไม่ ต้อง สร้างมือ นั่นคือ Ruby:
module Extensions::ActiveRecord::FindByOrderedIds extend ActiveSupport::Concern module ClassMethods def find_ordered(ids) order_clause = "CASE id " ids.each_with_index do |id, index| order_clause << sanitize_sql_array(["WHEN ? THEN ? ", id, index]) end order_clause << sanitize_sql_array(["ELSE ? END", ids.length]) where(id: ids).order(order_clause) end end end ActiveRecord::Base.include(Extensions::ActiveRecord::FindByOrderedIds) Person.find_ordered([2, 1, 3]) # => [2, 1, 3]
พอดีสิ่งที่อยาก!
วิธี MySQL-specific 乾淨
ถ้า用 MySQL มีวิธี乾淨กว่า MySQL 有 special ORDER BY FIELD syntax:
SELECT * FROM people WHERE id IN (1, 2, 3) ORDER BY FIELD(id, 2, 1, 3);
สร้างจาก Ruby ได้:
module Extensions::ActiveRecord::FindByOrderedIds extend ActiveSupport::Concern module ClassMethods def find_ordered(ids) sanitized_id_string = ids.map {|id| connection.quote(id)}.join(",") where(id: ids).order("FIELD(id, #{sanitized_id_string})") end end end ActiveRecord::Base.include(Extensions::ActiveRecord::FindByOrderedIds)
ถ้า用 MySQL และไม่กังวล compatibility มาก นี่วิธีดี อ่านง่ายตอน statements บินผ่าน logs
ตอนอยาก display records ในลำดับ arbitrary เฉพาะเจาะจง ไม่ต้อง sort ใน Ruby ด้วย code snippet นิดหน่อย ให้ database ทำสิ่งที่เก่ง: หา, เรียง, คืนข้อมูลให้ app