Chào các bạn
Khi mà mình tham gia dự án, mình có gặp một vấn đề khi mà viết câu lệnh để lấy dữ liệu tất cả đơn hàng sắp xếp theo gần với ngày hiện tại nhất sẽ lên đầu và càng xa ngày hiện tại sẽ xuống dưới. Tất nhiên là nếu có 1 cột nào đó trong bảng giá trị của mình ví dụ như day_until_due chẳng hạn cột này sẽ tính toán khoảng cách với ngày hiện tại thì mọi thứ thật dễ dàng khi mình có thể viêt

Order.order 'days_until_due ASC'

Nhưng mà đời không như là mơ, mình không thể tạo ra một cột như vậy và phải update tính toán là giá trị due_date lại hằng ngày như thế được. Thế là mình nghĩ ra việc viết với SQL mà ngày xưa có học

    order("(picked_date < utc_timestamp()),
      (case when picked_date>= utc_timestamp() then picked_date end) ASC,
      (case when picked_date < utc_timestamp() then picked_date end) DESC")

Mọi thứ có vẻ ổn nhưng cách này nhìn cũng khá củ chuối nhỉ. Vậy với một ngôn ngữ rất hay như ruby thì đâu thể bó tay với bài toán đơn giản này.
Let's started
Đầu tiên để sắp xếp được theo yêu cầu thì mình phải nghĩ là phải tính toán ngày tháng so với ngày hiện tại. Vậy là mở model Order ra viết cho bé nó 1 method để tính ngày so với hiện tại

def days_until_due
  today = Time.zone.now
  simple_today = Time.new today.year, today.month, today.day
  if picked_date.day >= today.day
    month_due = today.month
  else
    month_due = today.month + 1
  end
  day_due = Time.new today.year,  month_due, picked_date.day
  return ((day_due - simple_today)/(60*60*24)).to_i
end

Và tiếp tục chỉ viết đơn giản 1 dòng bao gồm cả việc query và sort như là

Order.all.sort_by(&:days_until_due)

Và unbelivable done. Mọi tính toán của mình đều đúng, vậy sort_by của ruby sao ghê quá vậy, ủa cái kí hiệu &: nhịn lạ vậy. Tại sao code mình lại chạy được nhỉ?

Vậy sort_by là gì? Method này lấy một block như một đối số và tạo ra một mảng được sắp xếp bằng cách mapping các giá trị thông qua block được cho. Nếu không có block được đưa ra, một Enumerator được trả về thay thế. Dưới đây là một ví dụ:

array = ["Tuan", "Teo", "Bi"]
# Sử dụng block
array.sort_by{|word| word.length} # => ["Bi", "Teo", "Tuan"] 

array.sort_by # => #<Enumerator: ["Bi", "Teo", "Tuan"]:sort_by>

Bây giờ bạn có thể tự hỏi mình, nhưng làm thế nào để '&: days_until_due' chuyển thành một block? Trong một câu, cú pháp '&' là chuyển proc thành một block, và : sẽ biến method tạo ra một proc. Vậy &: là đưa method của class đó thành 1 block đó là những gì Array # sort_by lấy như một đối số.
Sau đây là ví dụ

array = ["Tuan", "Teo", "Bi"]

# Tạo ra 1 proc và chuyển proc thành block sử dụng & syntax
proc = :length.to_proc # => #<Proc:0x007f98a225f700> 
array.sort_by(&proc) # => ["Bi", "Teo", "Tuan"]

Tạo ra 1 proc và chuyển proc thành 1 block

Cú pháp trên hoạt động bằng cách kết hợp implicit type với toán tử '&'. Toán tử '&' được sử dụng trong một danh sách các đối số để chuyển một thể hiện Proc sang block. Nếu bạn kết hợp các toán tử với một cái gì đó khác hơn là một Proc, implicit type sẽ cố gắng chuyển đổi nó sang một thể hiện Proc bằng cách sử dụng phương pháp to_proc. Kể từ ký hiệu # to_proc tồn tại, khi chúng ta thêm một biểu tượng sau toán tử '&', nó được chuyển thành một proc, sau đó chuyển thành một block.

Trở lại vấn đề ban đầu của tôi, tất cả những điều này nói về procs và blocks và là những gì cho phép tôi để tạo ra một dòng sau đây phương pháp:

Order.all.sort_by(&:days_until_due)

Happy coding. Have a nice day