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

184

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

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
  • 7
    прости меня, если я плотный, но как это поможет в этой ситуации?
  • 2
    попробуйте использовать «validates_uniqueness_of» в вашей модели. если это не сработает, попробуйте создать индекс, по которому вы можете создать миграцию файлов, которая включает в себя такой оператор, как add_index: table, [: column_a,: column_b],: unique => true)
Показать ещё 4 комментария
Теги:
activerecord
rails-activerecord
ruby-on-rails-3

4 ответа

289
Лучший ответ

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

validates_uniqueness_of :user_id, :scope => :friend_id
  • 81
    Просто хотел добавить, что вы можете передавать несколько параметров области действия в случае, если вам нужно проверить уникальность более чем в 2 полях. Т.е.: scope => [: friend_id,: group_id]
  • 26
    Странно, что вы не можете сказать validates_uniqueness_of [:user_id, :friend_id] . Может быть, это должно быть исправлено?
Показать ещё 4 комментария
108

Вы можете использовать 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 
  • 1
    Спасибо, сэр!!
31

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

validates_uniqueness_of :user_id, :scope => :friend_id

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

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

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

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

  • 3
    И как нам добиться ограничений на стороне DB (уникальных ограничений для нескольких столбцов) с помощью системы миграции Rails?
  • 0
    stackoverflow.com/questions/4123610/...
Показать ещё 2 комментария
0

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню