Guaranteeing unique values for a database column in Rails
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.