Sorting records fetched with DB limit does not work

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the dumb-mistakes-and-gotchas category.

Last Updated: 2024-11-21

The files on a product page were displayed in a bogus order. The order was insane, with 2012 appearing before 2008 and 2011 appearing after.

The sort code was sound:

@notes_files.
  group_by(&:author).
  sort_by {|group| group[1].map(&:created_at).max }.

However the controller that grabbed the notes file records, which I wrongly assumed was just @note_files = product.notes_files (and didn't check before deploying) had the following code that contained an SQL limit clause:

@notes_files = @product.notes_files.limit(15)

The limit applied before in-Ruby-memory sorting caused the ridiculous ordering. I fixed by doing the sorting in the DB, such that it would happen before the limit:

@notes_files = @product.notes_files.order(released_on: :desc).limit(15)

Aside

Although not super relevant for this lesson, in reality my fix didn't fully work at this point, because my association for notes_files in the Product model had a default scope that also screwed with this ordering.

class Product
  has_many :notes_files, -> { order(id: :desc) }
end

The fix was to remove this default ordering using reorder

@notes_files = @product.notes_files.reorder(released_on: :desc).limit(15)

Lessons

  1. Do not combine sort in memory with DB limit clauses. Do the sorting in the DB.

  2. When trying code in the console, ensure your trials matches the actual stack you will be using (i.e. if trying out view-level code, ensure it is a realistic example of the data in your controller and model code), rather than what you assume to be there... I assumed it was @product.notes_files but the actual controller had a limit clause attached to it.

  3. Undo default ordering scopes before ordering.