Ответ 1
Ваша миграция будет работать и позволит умножать значения null
(для большинства движков базы данных).
Но ваша проверка для класса пользователя должна выглядеть ниже.
validates :email, uniqueness: true, allow_nil: true
Я хочу указать уникальный индекс в столбце, но мне также нужно разрешить значения NULL
(у нескольких записей могут быть значения NULL
). При тестировании с помощью PostgreSQL я вижу, что у меня может быть 1 запись со значением NULL
, но в следующем случае возникает проблема:
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
Таким образом, даже если база данных позволяет это, Rails сначала проверяет, существует ли User
с другим идентификатором, а столбцом email
установлено значение NULL
. Есть ли способ, которым может позволить не только база данных, но и Rails не будут проверять сначала, как и выше?
Идея состоит в том, что пользователям не нужно вводить электронное письмо, но если это так, мне нужно найти пользователя по электронной почте. Я знаю, что могу создать еще одну модель, чтобы связать пользователей с электронными сообщениями, но я бы скорее сделал это выше.
UPDATE. Здесь код миграции, который я создал для добавления столбца email
:
class AddEmailToUsers < ActiveRecord::Migration
def change
add_column :users, :email, :string
add_index :users, :email, :unique => true
end
end
И вот код, который я добавил в модель User
:
validates :email, uniqueness: true
Я забыл, что добавил validates
вызов модели User
. Поэтому имеет смысл, что Rails проверяет сначала. Я предполагаю, что единственный вопрос заключается в том, безопасно ли для баз данных иметь уникальный индекс и поля NULL
? Есть ли способ указать в Rails, что я хочу проверить подлинность электронной почты, является уникальной, если она не была nil
?
Ваша миграция будет работать и позволит умножать значения null
(для большинства движков базы данных).
Но ваша проверка для класса пользователя должна выглядеть ниже.
validates :email, uniqueness: true, allow_nil: true
Чтобы выяснить, почему это работает на уровне базы данных, вам нужно понять трехзначную логику, используемую в SQL: true
, false
, null
.
null
обычно считается неизвестным, поэтому его семантика в операциях обычно эквивалентна незнанию того, что это за конкретное значение, и если вы все еще можете решить ответ. Так, например, 1.0 * null
- null
, но null OR true
- true
. В первом случае умножение на неизвестное неизвестно, но во втором, вторая половина условного выражения делает все утверждение всегда истинным, поэтому не имеет значения, что находится на левой стороне.
Теперь, когда дело доходит до индексов, стандарт не указывает ничего, что позволяет поставщикам интерпретировать неизвестные средства. Лично я считаю, что уникальный индекс должен быть определен как в документах PostgreSQL:
Когда индекс объявляется уникальным, несколько строк таблицы с равными индексированными значениями не будут разрешены
Тогда должен быть вопрос, каково значение null = null
? Правильный ответ должен быть null
. Поэтому, если вы немного прочитаете строки этих документов PostgreSQL и скажете, что уникальный индекс запретит несколько строк, для которых оператор равенства возвращает true для указанного значения, тогда должны быть разрешены несколько значений null
. Именно так работает PostgreSQL, поэтому в этой настройке вы можете иметь уникальный столбец с несколькими строками с null
в качестве значения.
С другой стороны, если вы хотите интерпретировать определение уникального индекса для запрещения нескольких строк, для которых оператор неравенства не возвращает false, вы не сможете иметь несколько строк с null
значениями. Кто предпочтет работать в этой противопожарной установке? Именно так Microsoft SQL Server определяет уникальный индекс.
Оба этих способа определения уникального индекса верны на основе стандартного определения SQL null
2003 года. Так что это действительно зависит от вашей базовой базы данных. Но, если сказать, я думаю, что большинство работает подобно PostgreSQL.