How to validate the uniqueness of an attribute across multiple models in Rails

I recently ran into the situation where I had multiple models that each had a identifier field, and each object of each model had to have a unique value for this field.

Now, in my specific situation, I could have used UUIDs, because I was using Postgres and the identifier didn't have any other requirements apart from being unique, but there is a nice generic way to implement uniqueness checks across models in Rails, which is what I chose to use.

The code

Let's look at the code first:

# app/models/concerns/validate_identifier_uniqueness_across_models.rb
module ValidateIdentifierUniquenessAcrossModels
  extend ActiveSupport::Concern

  @@included_classes = []

  included do
    @@included_classes << self
    validate :identifier_unique_across_all_models


  def identifier_unique_across_all_models
    return if self.identifier.blank?
    @@included_classes.each do |klass|
      scope = klass.where(identifier: self.identifier)
      if self.persisted? && klass == self.class
        scope = scope.where.not(id:
      if scope.any?
        self.errors.add :identifier, 'is already taken'

# app/models/product.rb
class Product < ActiveRecord::Base
  include ValidateIdentifierUniquenessAcrossModels

# app/models/category.rb
class Category < ActiveRecord::Base
  include ValidateIdentifierUniquenessAcrossModels

So what's happening here? We defined a Concern with the (quite verbose) name of ValidateIdentifierUniquenessAcrossModels and included it in our Product and Category models. Once a model includes this concern, two things happen:

  • the model is added to the Concern's class variable @@included_classes, and
  • a validation is added

Let's validate together

Let's look at the validation method identifier_unique_across_all_models more closely. What we want to do in this method is to look at all models that have included ValidateIdentifierUniquenessAcrossModels and check each object of each model for a matching identifier. If we find only one object with the identifier of the object we're validating, we add an error message to the identifier field and stop checking (using break).

One thing to make sure is that we're excluding the object that is being validated, in case it is already persisted, since otherwise the object under validation will be found in the database and mistaken for a duplicate and thus the validation will always fail.

Reusable and testable

The beauty of packaging up this logic in a Concern is that it can be added to more models later on effortlessly (imagine a User model, which also needs to have unique identifiers, is added to your app later on. Simply include ValidateIdentifierUniquenessAcrossModels in that model and you're good to go) but also that it can be quite simply tested out of the context of a product or category object (in your tests, simply create a new model, include ValidateIdentifierUniquenessAcrossModels, and test the behavior of that model).

Caveat: race condition

As Jan de Poorter points out in the comments, this approach has a race condition: between checking for existing identifiers and creating/updating the record, there is a small window where a duplicate can be created. So if you're creating lots of records simultaneously, you might end up with duplicates after all. Keep that in mind when using this method!

Discuss this post on Hacker News

Ideas? Constructive criticism? Think I'm stupid? Let me know in the comments!