Rails validations: unique within a certain scope
Tags: ruby, rails, webdev
Published: 2021-03-16 14:24:21

---
title: Rails validations: unique within a certain scope
published: true
date: 2021-03-16 13:00:00 UTC
tags: ruby,rails,webdev
canonical_url: https://boringrails.com/tips/rails-unique-scope
---

It’s a great idea to make your database and application validations match. If you have `validates :name, presence: true` in your model, you should pair it with a `not null` database constraint. Unique validations should be paired with a `UNIQUE` database index.

In real-world applications, you often have more complicated validations, but you should continue this practice whenever you can.

Something I encounter regularly is the need to have records that are unique, but within a certain scope.

Imagine you were building a typical project management tool. You might want `Project`s to have a unique name so they can be distinguished within your UI – but you don’t want the name to be globally unique. If I make a project called “Onboarding”, another customer should not be restricted from using that name as well.

Luckily, Rails has got us covered with a handy feature called **validation scopes**.

## Usage

The `scope` option to the Rails uniqueness validation rule allows us to specify additional columns to consider when checking for uniqueness.

```ruby
class Project < ApplicationRecord
  belongs_to :account

  has_many :tasks

  validates :name, presence: true, uniqueness: { scope: :account_id }
end
```

This rule says that “the name of this project must unique, within the scope of this account”. In other words, the combination of a `name` and `account_id` must be unique – but you can have projects with the same name in different accounts.

As we discussed earlier, you really want to back-up your application level validations with database constraints.

In this case, you’ll want to do a multiple column index. You can do this in a normal Rails migration.

```ruby
class CreateProject < ActiveRecord::Migration[6.0]
  def change
    create_table :projects do |t|
      ...
    end

    add_index :projects, [:name, :account_id], unique: true
  end
end
```

## Options

You can pass multiple columns to `scope`.

If you were building a dining app and wanted to enforce that a guest could only have one reservation at a restaurant per day.

```ruby
class Reservation < ApplicationRecord
  belongs_to :guest
  belongs_to :restaurant

  validates :guest_id, uniqueness: {
    scope: [:restaurant_id, :reservation_date]
  }
end
```

You may wish to change the message since the defaults error message will be fairly spartan: “{field} has already been taken”

```ruby
validates :guest_id, uniqueness: {
  scope: [:restaurant_id, :reservation_date],
  message: "Only one reservation per guest per day is permitted"
}
```

Note: In PostgreSQL, the default limit for index names is 63 characters so you may find yourself needing to change the index name if your model or column names are longer.

```ruby
add_index :reservations, [:guest_id, :restaurant_id, :reservation_date],
  unique: true,
  name: "idx_reserveration_guest_date_uniq"
```

## Additional Resources

Rails API: [Uniqueness Validations](https://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of)

PostgreSQL Docs: [Postgres Constraints](https://www.postgresql.org/docs/current/ddl-constraints.html)

MySql Docs: [Multi-column Indexes](https://dev.mysql.com/doc/refman/8.0/en/multiple-column-indexes.html)

* * *
[![](https://boringrails.com/images/twitter-banner.png)](https://twitter.com/_swanson)