Есть ли рельсовый способ проверки того, что фактическая запись уникальна, а не только столбец? Например, модель/таблица дружбы не должна иметь несколько одинаковых записей, например:
user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
Есть ли рельсовый способ проверки того, что фактическая запись уникальна, а не только столбец? Например, модель/таблица дружбы не должна иметь несколько одинаковых записей, например:
user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
Вы можете объединить вызов 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]}
Однако приведенные выше подходы к проверке имеют условие гонки и не могут обеспечить согласованность. Рассмотрим следующий пример:
Записи таблицы базы данных должны быть уникальными для 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
Вероятно, вам нужны фактические ограничения для db, потому что проверка достоверности зависит от условий гонки.
validates_uniqueness_of :user_id, :scope => :friend_id
Когда вы сохраняете экземпляр пользователя, Rails проверит вашу модель, запустив запрос SELECT, чтобы узнать, существуют ли какие-либо записи пользователя с предоставленным user_id. Предполагая, что запись окажется действительной, Rails запускает инструкцию INSERT для сохранения пользователя. Это отлично работает, если вы запускаете один экземпляр одного веб-сервера процесса/потока.
В случае, если два процесса/потоки пытаются создать пользователя с одним и тем же user_id примерно в одно и то же время, может возникнуть следующая ситуация.
С уникальными индексами на db, ситуация выше будет проиллюстрирована следующим образом.
Ответ, взятый из этого сообщения в блоге - http://robots.thoughtbot.com/the-perils-of-uniqueness-validations
Ограничение базы данных:
add_index :friendships, [:user_id, :friend_id], unique: true