Validar la unicidad de varias columnas


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

user_id: 10 | friend_id: 20
user_id: 10 | friend_id: 20
Author: potashin, 2011-02-02

4 answers

Puede definir el alcance de una llamada validates_uniqueness_of de la siguiente manera.

validates_uniqueness_of :user_id, :scope => :friend_id
 310
Author: Dylan Markow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-03 17:38:35

Puede utilizar validates para validar uniqueness en una columna:

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

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

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

Sin embargo, los enfoques de validación mostrados anteriormente tienen una condición de carrera y no pueden garantizar la consistencia. Considere el siguiente ejemplo:

  1. Se supone que los registros de tablas de 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 esté utilizando ), acceda 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 el mismo datos.

Para evitar este tipo de comportamiento, se debe agregar una restricción única a la tabla db. Puede configurarlo con add_index helper para uno(o varios) campos 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 la base de datos, pero en lugar de crear registros duplicados, esto ActiveRecord::RecordNotUnique excepción, que usted debe manejar por separado:

begin
# writing to database
rescue ActiveRecord::RecordNotUnique => e
# handling the case when record already exists
end 
 120
Author: potashin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-20 16:36:29

Probablemente necesite restricciones reales en la base de datos, porque validates sufre de condiciones de carrera.

validates_uniqueness_of :user_id, :scope => :friend_id

Cuando persista una instancia de usuario, Rails validará su modelo ejecutando una consulta SELECT para ver si ya existen registros de usuario con el user_id proporcionado. Suponiendo que el registro sea válido, Rails ejecutará la instrucción INSERT para persistir al usuario. Esto funciona muy bien si está ejecutando una sola instancia de un solo servidor web de proceso/hilo.

En el caso dos los procesos / subprocesos están tratando de crear un usuario con el mismo user_id alrededor del mismo tiempo, la siguiente situación puede surgir. Condición de carrera con validates

Con índices únicos en la base de datos en su lugar, la situación anterior se desarrollará de la siguiente manera. Índices únicos en db

Respuesta tomada de este post del blog - http://robots.thoughtbot.com/the-perils-of-uniqueness-validations

 33
Author: Deepak Azad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-17 18:14:17

Esto se puede hacer con una restricción de base de datos en las dos columnas:

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

Podría usar un validador rails, pero en general recomiendo usar una restricción de base de datos.

Más información: https://robots.thoughtbot.com/validation-database-constraint-or-both

 1
Author: Tate Thurston,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-01 22:01:50