Michael Anhari

Guaranteeing unique values for a database column in Rails

A green blueberry in a pile of ripe ones

Today will be a quick one. Let's talk about ensuring the integrity of the data in your application's database at the ORM level with ActiveRecord, and at the database level. We'll use a common feature request, ensuring some column is unique, as an example.

ActiveRecord unique validations

ActiveRecord provides an easy interface for writing validations that will try to ensure new entries into your database will have a unique value for a given column. Looking at our Article model from the other day:

class Article < ApplicationRecord
  validates :title, presence: true, uniqueness: true
  validates :body, presence: true
end

This small snippet of code will check the database for an existing article with the same title before trying to commit it to the database. If one already exists, it adds a nice error message to our object to invalidate it.

Race conditions

Unfortunately, this isn't enough to guarantee that this column stays unique. What if two records are simultaneously submitted and our unique validations both don't find an existing record in the database? Both are given the all-clear and end up as new records in our DB.

Validating at the database level

There's only one true way of making this business need a guarantee, and that's by moving down the stack from the ORM level and to the database level. To guarantee uniqueness of our articles title column, we'll need to build a unique index in Postgres. Adding a unique index with a Rails migration is simple:

class AddUniqueIndexForArticleTitles < ActiveRecord::Migration[6.0]
  add_index :articles, :title, unique: true
end

Now our minds can be at peace.

Which do I use?

Always use the Postgres unique index to guarantee uniqueness. Adding the ActiveRecord validation really depends on whether you need to display a validation message to the user, which you probably want to do for the majority of use cases.

Newsletter

I'm working on sending out a weekly newsletter. I'll make it as easy as possible to unsubscribe at any time.