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

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

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

4 ответов


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

validates_uniqueness_of :user_id, :scope => :friend_id

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

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

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

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

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

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

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

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

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

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

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

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

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

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

validates_uniqueness_of :user_id, :scope => :friend_id

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

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

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

ответ взят из этого блога - http://robots.thoughtbot.com/the-perils-of-uniqueness-validations


Это можно сделать с ограничением базы данных для двух столбцов:

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

вы можете использовать валидатор rails, но в целом я рекомендую использовать ограничение базы данных.

больше чтения:https://robots.thoughtbot.com/validation-database-constraint-or-both