Order by created_at and updated_at with scopes and a concern

I’ve suggested before that it’s a good idea to keep calls to the query API within models and Rails provides scopes as a way to encapsulate regularly-used queries.

Rails also includes Active Support’s Concerns as a way to group functionality that’s used in multiple places. It’s a wrapper around a standard Ruby module.

Something we often want to do in multiple places in our applications is sorting models by the time they are created or updated, using the built-in timestamps.

Let’s combine these two techniques to provide a useful mini library in our application to provide this functionality.

Rather than…

…ordering by timestamps using scopes:

app/controllers/books_controller.rb

class BooksController
  def index
    @books = Book.order(created_at: :asc).limit(20)
  end
end

Or, better, using a named scope:

app/models/book.rb

class Book < ApplicationRecord
  scope :by_recently_created, -> { order(created_at: :desc) }
end

app/controllers/books_controller.rb

class BooksController
  def index
    @books = Book.by_recently_created.limit(20)
  end
end

Use…

…this handy concern:

app/models/concerns/orderable_by_timestamp.rb

module OrderableByTimestamp
  extend ActiveSupport::Concern

  included do
    scope :by_earliest_created, -> { order(created_at: :asc) }
    scope :by_recently_created, -> { order(created_at: :desc) }
    scope :by_earliest_updated, -> { order(updated_at: :asc) }
    scope :by_recently_updated, -> { order(updated_at: :desc) }
  end
end

app/models/book.rb

class Book < ApplicationRecord
  include OrderableByTimestamp
end

app/controllers/books_controller.rb

class BooksController
  def index
    @books = Book.by_recently_created.limit(20)
  end
end

And now that you have the scopes extracted into a concern, you can reuse OrderableByTimestamp in multiple models.

Why?

This common refactor showcases the magic of multiple parts of Rails working together

We’ve taken advantage of these useful features to extract regularly reused functionality into one place. You might see this referred to as DRY-ing up your code.

Why not?

Including four fairly generic scopes into multiple models, if you don’t use every scope, might be expanding the model unnecessarily. This is a reason to keep any concerns you build as small as you can.

Hat Tip

Dan pulled together this pattern in our applications at CoverageBook & AnswerThePublic.

Last updated on April 26th, 2021