type rails postgres example column ruby-on-rails postgresql activerecord ruby-on-rails-4 rails-activerecord

ruby on rails - postgres - ¿Es posible especificar un índice único con NULL permitido en Rails/ActiveRecord?



rails postgresql jsonb (2)

Quiero especificar un índice único en una columna, pero también necesito permitir valores NULL (varios registros pueden tener valores NULL ). Cuando pruebo con PostgreSQL, veo que puedo tener 1 registro con un valor NULL , pero el siguiente causará un problema:

irb(main):001:0> u=User.find(5) User Load (111.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 5]] => #<User id: 5, email: "[email protected]", created_at: "2013-08-28 09:55:28", updated_at: "2013-08-28 09:55:28"> irb(main):002:0> u.email=nil => nil irb(main):003:0> u.save (1.1ms) BEGIN User Exists (4.8ms) SELECT 1 AS one FROM "users" WHERE ("users"."email" IS NULL AND "users"."id" != 5) LIMIT 1 (1.5ms) ROLLBACK => false

Entonces, incluso si la base de datos lo permite, Rails primero verifica si existe un User con una identificación diferente y con la columna de email establecida en NULL . ¿Hay alguna manera de que no solo la base de datos pueda permitirlo, sino que Rails no verificará primero también como arriba?

La idea es que los usuarios no tengan que ingresar un correo electrónico, pero si lo hacen, necesito poder encontrar un usuario por correo electrónico. Sé que puedo crear otro modelo para asociar usuarios a correos electrónicos, pero preferiría hacerlo de la manera anterior.

ACTUALIZACIÓN : Aquí está el código de migración que había creado para agregar la columna de email :

class AddEmailToUsers < ActiveRecord::Migration def change add_column :users, :email, :string add_index :users, :email, :unique => true end end

Y aquí está el código que agregué al modelo de User :

validates :email, uniqueness: true

Olvidé que había agregado la llamada de validates al modelo de User . Entonces tiene sentido que Rails esté revisando primero. Supongo que la única otra pregunta es si es seguro que las bases de datos tengan un índice único y campos NULL . ¿Existe alguna manera de especificar en Rails que deseo validar que el correo electrónico sea único a menos que sea nil ?


Para aclarar por qué esto funciona en el nivel de la base de datos, debe comprender la lógica de tres valores utilizada en SQL: true , false , null .

Normalmente, se considera que null es desconocido, por lo tanto, su semántica en las operaciones suele equivaler a no saber cuál es ese valor particular y ver si aún puede encontrar una respuesta. Entonces, por ejemplo, 1.0 * null es null pero null OR true es true . En el primer caso, la multiplicación por un desconocido se desconoce, pero en el segundo, la segunda mitad del condicional hace que el enunciado sea siempre verdadero, por lo que no importa lo que esté en el lado izquierdo.

Ahora, cuando se trata de índices, el estándar no especifica nada, por lo que los proveedores quedan para interpretar lo que significa "desconocido". Personalmente, creo que se debe definir un índice único como en los documentos de PostgreSQL:

Cuando un índice se declara único, no se permitirán varias filas de tabla con los mismos valores indexados

La pregunta debería ser ¿cuál es el valor de null = null ? La respuesta correcta debe ser null . Entonces, si lee un poco entre las líneas de esos documentos de PostgreSQL y dice que un índice único no permitirá múltiples filas para las cuales el operador de igualdad devuelve verdadero para dicho valor, entonces se deben permitir múltiples valores null . Así es exactamente como funciona PostgreSQL, entonces en esa configuración puede tener una columna única con múltiples filas que tengan null como valor.

Por otro lado, si desea interpretar la definición de un índice único para no permitir múltiples filas para las cuales el operador de desigualdad no devuelve falso, entonces no podrá tener múltiples filas con valores null . ¿Quién elegiría operar en esta configuración contrapositiva? Así es como Microsoft SQL Server elige definir un índice único.

Ambas formas de definir un índice único son correctas basadas en la definición de null de la norma SQL de 2003. Entonces realmente depende de tu base de datos subyacente. Pero dicho esto, creo que la mayoría opera de manera similar a PostgreSQL.


Su migración funcionará y permitirá multiplicar valores null (para la mayoría de los motores de base de datos).

Pero su validación para la clase de usuario debería verse a continuación.

validates :email, uniqueness: true, allow_nil: true