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