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

193

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

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
re5et
источник
7
прости меня, если я плотный, но как это поможет в этой ситуации?
re5et
2
попробуйте использовать «validates_uniqueness_of» в вашей модели. если это не сработает, попробуйте создать индекс, по которому вы можете создать миграцию полей, которая включает в себя такой оператор, как add_index: table, [: column_a,: column_b],: unique => true)
Harry Joy
1
@HarryJoy, спросил он Is there a rails-way way. И вы предлагаете ему не рельсовый путь, а стандартный. The Active Record way claims that intelligence belongs in your models, not in the database.
Зеленый,
2
К сожалению validates :field_name, unique: true, склонен к гоночным условиям, поэтому, несмотря на рельсовые пути, предпочтение отдается фактическим ограничениям. @HarryJoy Я опишу ответ, описывающий способ ограничения.
Пооян Хосрави
1
лучший ответ, тогда все, что указано ниже - вот этот stackoverflow.com/a/34425284/1612469, поскольку он приносит еще один слой, чтобы убедиться, что все будет работать правильно
Aleks

Ответы:

319

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

validates_uniqueness_of :user_id, :scope => :friend_id
Дилан Марков
источник
83
Просто хотел добавить, что вы можете передавать несколько параметров области действия в случае, если вам нужно проверить уникальность более чем в 2 полях. Т.е.: scope => [: friend_id,: group_id]
Дейв Рапин
27
Странно, что ты не можешь сказать validates_uniqueness_of [:user_id, :friend_id]. Может быть, это должно быть исправлено?
Алексей
12
Алексей, validates_uniqueness_of [: user_id,: friend_id] просто выполнит проверку для каждого из перечисленных полей - и это задокументировано и ожидаемое поведение
Никита Хисматов
71
В Rails 4 это становится: validates: user_id, уникальность: {scope:: friend_id}
Marina Martin
3
Вы, вероятно, хотите добавить сообщение об ошибке, например: message => 'уже есть этот друг.'
laffuste
137

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

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

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

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

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

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

  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 
potashin
источник
2

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

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

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

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

Тейт Терстон
источник