Configure default ordering on models with UUID primary keys in ActiveRecord
In Rails 6 it's possible to configure the implicit order of the first
and last
methods, which is 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’ss possible in Rails 6 where you can now configure the implicit order used by the first
and last
finder methods:
class Post < ActiveRecord::Base
self.implicit_order_column = "created_at"
end
For more details here’s the pull request for this change.
Why not use a default scope?
You might 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
If you configure the implicit order to a non-unique column such as the created_at
timestamp, there’s a caveat you should keep in mind: if you have two records that end up with identical timestamp then you have no guarantee that the database will return the same record every time you call the finder method. This should be relatively unlikely given Rails and PostgreSQL stores DateTimes with microsecond precision, but keep it in mind all the same.