14 Dec 2018

Configure the default ordering for your models using UUID for primary keys

TL;DR: In Rails 6 it’ll be possible to configure the implicit order column used by the first and last methods, handy if you use UUIDs for your primary keys.

You may be using UUIDs as the primary key type for your models in your Rails application. Andy Croll has a great post explaining why you might choose to do this.

If you do then you’re probably aware of one of the downsides, which is that calling ActiveRecord’s first and last finder methods to get the oldest or newest record leads to surprising results. This is because the implementations of first and last rely on the primary key being an auto-incrementing integer, so calling them on a model with UUIDs as primary key without an explicit order scope results in a seemingly random record being returned (in reality it’ll be the record with the numerically-highest or lowest UUIDs, but that’s unlikely to be of any help to anyone)

Whilst always being explicit and including an order scope when calling first and last is a good habit to develop, having a sensible default behaviour would also be nice. Thankfully that’ll be possible in Rails 6 where the implicit order used by the first and last finder methods will be configurable:

  class Post < ActiveRecord::Base
    self.implicit_order_column = "created_at"
  end
  

For more details here’s the pull request for this change.

Why not just use a default scopes?

You be tempted to solve this issue using a default scope instead, something like this:

  class Post < ActiveRecord::Base
    default_scope { order(:created_at) }
  end
  

Please don’t. Default scopes can lead to all sorts of strange bugs and confusing behaviour. For example, with the above default scope on the model you’ll find calling Post.order(:name) will behave in a surprising way: the records will be returned ordered by created_at and then name, rather than just by name.

A caveat

There is a caveat to be aware of if you configure the implicit order to a non-unique column such as the created_at timestamp: if you have two records that have identical timestamp then you have no guarantee that the database will return the same record every time you call the finder method. Given that Rails and PostgreSQL stores DateTimes with microsecond precision the chances of this happening are relatively small, but you should still be aware of it.