Validar la unicidad de múltiples columnas

193

¿Hay alguna manera de validar que un registro real sea único y no solo una columna? Por ejemplo, un modelo / tabla de amistad no debería poder tener múltiples registros idénticos como:

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
re5et
fuente
77
perdóname si estoy siendo denso, pero ¿cómo ayudaría eso en esta situación?
re5et
2
intente usar "validates_uniqueness_of" en su modelo. si esto no funciona, intente crear un índice en el que pueda crear una migración de campos que incluya una declaración como add_index: table, [: column_a,: column_b],: unique => true)
Harry Joy
1
@HarryJoy, preguntó Is there a rails-way way. Y le ofreces un camino sin rieles, pero estándar. The Active Record way claims that intelligence belongs in your models, not in the database.
Verde
2
Lamentablemente, validates :field_name, unique: truees propenso a las condiciones de carrera, por lo que, aunque en contra de los rieles, se prefiere una restricción real. @HarryJoy Votaré una respuesta describiendo la forma de restricción.
Pooyan Khosravi
1
mejor respuesta que todo lo que se indica a continuación es este stackoverflow.com/a/34425284/1612469, ya que trae otra capa para asegurarse de que todo funcione correctamente
Aleks

Respuestas:

319

Puede abarcar una validates_uniqueness_ofllamada de la siguiente manera.

validates_uniqueness_of :user_id, :scope => :friend_id
Dylan Markow
fuente
83
Solo quería agregar que puede pasar múltiples parámetros de alcance en caso de que necesite validar la unicidad en más de 2 campos. Es decir: Ámbito => [: friend_id,: group_id]
David Rapin
27
Extraño que no puedas decir validates_uniqueness_of [:user_id, :friend_id]. Tal vez esto necesita ser parcheado?
Alexey
12
Alexey, validates_uniqueness_of [: user_id,: friend_id] solo hará la validación para cada uno de los campos enumerados, y es un comportamiento documentado y esperado
Nikita Hismatov
71
En Rails 4, esto se convierte en: valida: user_id, unicidad: {scope:: friend_id}
Marina Martin
3
Probablemente desee agregar un mensaje de error personalizado como: message => 'ya tiene este amigo'.
laffuste
137

Puede usar validatespara validar uniquenessen una columna:

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

La sintaxis para la validación en varias columnas es similar, pero en su lugar debe proporcionar una matriz de campos:

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

Sin embargo , los enfoques de validación que se muestran arriba tienen una condición de carrera y no pueden garantizar la coherencia. Considere el siguiente ejemplo:

  1. se supone que los registros de la tabla de la base de datos son únicos por n campos;

  2. múltiples ( dos o más ) solicitudes concurrentes, manejadas por procesos separados cada una ( servidores de aplicaciones, servidores de trabajo en segundo plano o lo que sea que esté usando ), accedan a la base de datos para insertar el mismo registro en la tabla;

  3. cada proceso en paralelo valida si hay un registro con los mismos n campos;

  4. la validación para cada solicitud se pasa con éxito y cada proceso crea un registro en la tabla con los mismos datos.

Para evitar este tipo de comportamiento, se debe agregar una restricción única a la tabla db. Puede configurarlo con add_indexayuda para uno (o varios) campo (s) ejecutando la siguiente migración:

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

Advertencia : incluso después de haber establecido una restricción única, dos o más solicitudes simultáneas intentarán escribir los mismos datos en db, pero en lugar de crear registros duplicados, esto generará una ActiveRecord::RecordNotUniqueexcepción, que debe manejar por separado:

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