Write unique validation with confidence!

It’s easy to use model validation in Rails application and in this article, we will study some aspects with the uniqueness validator.

So let’s start with an easy requirement, we want to have an Account model with a unique email attribute.

class Account < ApplicationRecord

   validates :email, uniqueness: true

end

This solution works but it’s not enough. For beginners, it’s important to understand how validation work in Rails. So from Rails guide, we can check the following:

Creating and saving a new record will send an SQL INSERT operation to the database. Updating an existing record will send an SQL UPDATE operation instead. Validations are typically run before these commands are sent to the database.

As we can see from the quote, if we submit two duplicate models at the same time it’s possible to bypass this validation. To test this scenario we will use Parallel gem, just add the following line to your Gemfile and don’t forget to run bundle install

gem 'parallel'

For testing purpose we will add this method in Account class:

class Account < ApplicationRecord

  validates :email, uniqueness: true

  def self.create_in_parallel
    emails = [{email: 'test@test.com'},{email: 'test@test.com'}]
    Parallel.each(emails) do |email|
      Account.create(email)
    end
  end

end

And now from Rails console:

Account.create_in_parallel
Account.all

what a surprise! We have duplicate records now. To fix this issue we just need to add a unique database index for email column.

class AddIndexToAccounts < ActiveRecord::Migration[5.0]
  def change
    add_index :accounts, :email, unique: true
  end
end

That’s it, with this simple approach we have a less issue to worry about 🙂