Validating Uniqueness of with a Scope

I have a project which has many users through: a project_members association.

Here is the code:

class User < ApplicationRecord
	has_many :project_members
	has_many :projects, through: :project_members
end

class ProjectMember < ApplicationRecord
	belongs_to :project
  	belongs_to :user

  	validates :project, uniqueness: { scope: :user}
end

class Project
	has_many :project_members
	has_many :users, through: :project_members
end

What’s wrong with this?

This protects things simply in the application / or model layer. There are instances - very rare instances, where your app is under tremendous load. You may stumble upon a situation where you have two project members that are identical. Debugging those sorts of things at scale would be a night mare.

You can avoid those problems by adding a database constraint:

class CreateProjectMembers < ActiveRecord::Migration[6.0]
  def change
    create_table :project_members do |t|
      t.references :project, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true

      t.timestamps
    end

    ## Add this constraint here:
    add_index :project_members, [:user_id, :project_id], unique: true
  end
end

That will ensure that the database will absolutely reject any duplicates. Add a database constraint. Then you’re safe. Unfortunately, abstractions are leaky.

(I’ve hacked this together quickly without testing it, so there may be a few errors. Please let me know if you’ve found any!)

Written on January 27, 2020