Проверка уникальности нескольких столбцов

Есть ли рельсовый способ проверки того, что фактическая запись уникальна, а не только столбец? Например, модель/таблица дружбы не должна иметь несколько одинаковых записей, например:

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20

Ответ 1

Вы можете объединить вызов validates_uniqueness_of следующим образом.

validates_uniqueness_of :user_id, :scope => :friend_id

Ответ 2

Вы можете использовать validates для проверки uniqueness в одном столбце:

validates :user_id, uniqueness: {scope: :friend_id}

Синтаксис проверки для нескольких столбцов аналогичен, но вместо этого вы должны указать массив полей:

validates :attr, uniqueness: {scope: [:attr1, ... , :attrn]}

Однако приведенные выше подходы к проверке имеют условие гонки и не могут обеспечить согласованность. Рассмотрим следующий пример:

  • Записи таблицы базы данных должны быть уникальными для n полей;

  • несколько (двух или более) одновременных запросов, обрабатываемых отдельными процессами каждый (серверы приложений, серверы рабочего стола или все, что вы используете), доступ к базе данных для вставки одной и той же записи в таблицу;

  • каждый параллельный процесс проверяет, есть ли запись с теми же n полями;

  • проверка для каждого запроса успешно передается, и каждый процесс создает запись в таблице с теми же данными.

Чтобы избежать такого поведения, нужно добавить уникальное ограничение для таблицы db. Вы можете установить его с помощью add_index помощника для одного (или нескольких) полей, выполнив следующую миграцию:

class AddUniqueConstraints < ActiveRecord::Migration
  def change
   add_index :table_name, [:field1, ... , :fieldn], unique: true
  end
end

Предостережение: даже после того, как вы установили уникальное ограничение, два или более одновременных запроса будут пытаться записать одни и те же данные в db, но вместо создания повторяющихся записей это приведет к ActiveRecord::RecordNotUnique, которое вы должны обрабатывать отдельно:

begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end 

Ответ 3

Вероятно, вам нужны фактические ограничения для db, потому что проверка достоверности зависит от условий гонки.

validates_uniqueness_of :user_id, :scope => :friend_id

Когда вы сохраняете экземпляр пользователя, Rails проверит вашу модель, запустив запрос SELECT, чтобы узнать, существуют ли какие-либо записи пользователя с предоставленным user_id. Предполагая, что запись окажется действительной, Rails запускает инструкцию INSERT для сохранения пользователя. Это отлично работает, если вы запускаете один экземпляр одного веб-сервера процесса/потока.

В случае, если два процесса/потоки пытаются создать пользователя с одним и тем же user_id примерно в одно и то же время, может возникнуть следующая ситуация. Race condition with validates

С уникальными индексами на db, ситуация выше будет проиллюстрирована следующим образом. Unique indexes on db

Ответ, взятый из этого сообщения в блоге - http://robots.thoughtbot.com/the-perils-of-uniqueness-validations

Ответ 4

Ограничение базы данных:

add_index :friendships, [:user_id, :friend_id], unique: true