Rails& SQLite:迁移以添加唯一索引并允许空值

时间:2014-04-02 19:53:18

标签: ruby-on-rails

我想强制电子邮件在数据库级别是唯一的,但我也想允许空电子邮件值。迁移时出现以下错误。

当前架构

  create_table "users", force: true do |t|
    t.string   "email",                  default: ""
    ...
  end

add_index "users", ["email"], name: "index_users_on_email"

尝试迁移

remove_index :users, [:email]
change_column :users, :email, :string, :null => true
add_index :users, [:email], unique: true

迁移错误

==  RemoveUniqueEmailFromUsers: reverting =====================================
-- remove_index(:users, [:email])
   -> 0.0437s
-- change_column(:users, :email, :string, {:null=>true})
   -> 0.2664s
-- add_index(:users, [:email], {:unique=>true})
rake aborted!
An error has occurred, this and all later migrations canceled:

SQLite3::ConstraintException: indexed columns are not unique: CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email")

3 个答案:

答案 0 :(得分:0)

尝试删除default: ""。我认为你有矛盾的配置。 空字符串不是零。

答案 1 :(得分:0)

问题是我创建的最后一个用户收到了''的电子邮件。在销毁此用户后,迁移继续进行。

答案 2 :(得分:0)

如果您在运行SQLite::ConstraintException之后看到$ rake db:migrate

SQLite::ConstraintException : indexed columns are not unique CREATE UNIQUE INDEX "index_users_on_email" ON "users"

...并且您在开发环境中工作,并且您不需要开发数据库中的数据,您可以相当快地解决此问题。

您可以查看数据库中是否有现有用户,并将其全部删除:

$ rails console
> User.count
=> 4
> User.destroy_all
> ...
> User.count
=> 0

然后,您可以再次运行$ rake db:migrate,并且迁移应该成功运行。

如果您不确定,可以运行$ rake db:migrate:status。如果所有迁移都是" up,"然后您的迁移已成功运行。

以下是如何理解问题:

  1. 您的现有用户没有电子邮件值(用户表没有电子邮件属性)。

  2. 运行迁移时,第一部分会添加电子邮件列。

  3. 现在,您的所有用户都有一个电子邮件属性,但数据库中的值为空。

  4. 迁移的最后一部分尝试添加索引,条件是值是唯一的(unique: true指定的内容)。

  5. 唯一约束无法添加到数据库,因为您现在有一个users表的电子邮件列,它们都具有空值,使其成为非唯一值。 SQLite抛出错误,声明您无法向具有非唯一值的列添加唯一约束。

  6. 如果您尝试在生产模式下将此约束添加到数据库列,这可能是一件好事!感谢您保护我们,SQLite!

    一旦你理解了这个问题,就可以看到还有其他可能的解决方案,但是如果你在新应用程序的开发环境中,这可能会满足你的需求。