178

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

user_id: 10 | friend_id: 20 
user_id: 10 | friend_id: 20 
+7

простите меня, если я плотный, но как это поможет в этой ситуации? – re5et

+2

попробуйте использовать «validates_uniqueness_of» в вашей модели. если это не работает, попробуйте создать индекс, на котором вы можете создать миграцию feilds, которая включает в себя инструкцию типа add_index: table, [: column_a,: column_b],: unique => true) –

+1

@HarryJoy, он спросил: Есть рельсовые пути ». И вы предлагаете ему путь без рельсов, но стандартный. 'Способ Active Record утверждает, что интеллект принадлежит вашим моделям, а не в базе данных.' – Green

ответ

306

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

validates_uniqueness_of :user_id, :scope => :friend_id 
+78

Просто хотел добавить, что вы можете передать несколько параметров области в случае вам нужно проверить уникальность на более чем 2-х полях. То есть : scope => [: friend_id,: group_id] –

+25

Странно, что вы не можете сказать 'validates_uniqueness_of [: user_id,: friend_id]'. Может быть, это должно быть исправлено? – Alexey

+11

Alexey, validates_uniqueness_of [: user_id,: friend_id] будет просто выполнять проверку для каждого из перечисленных полей - и это документированное и ожидаемое поведение –

32

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

validates_uniqueness_of :user_id, :scope => :friend_id 

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

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

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

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

+3

И как мы достигаем ограничений на стороне БД (уникальной сдержанности среди нескольких столбцов) с системой миграции Rails? –

+0

http://stackoverflow.com/questions/4123610/how-to-implement-a-unique-index-on-two-columns-in-rails –

+0

@LeeHanKyeol, чтобы сделать это, вы можете сделать что-то вроде def change add_index : users,: email, unique: true end end –

113

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

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

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

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

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

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

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

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

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

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

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 
+1

Спасибо, сэр! – nuc

0

DB Constraint:

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

Смежные вопросы