Rails / ActiveRecord中不区分大小写的唯一索引?

我需要在rails中的列上创建一个不区分大小写的索引。 我是通过SQL做到的:

execute( "CREATE UNIQUE INDEX index_users_on_lower_email_index ON users (lower(email))" ) 

这很好用,但在我的schema.rb文件中,我有:

 add_index "users", [nil], :name => "index_users_on_lower_email_index", :unique => true 

注意“零”。 因此,当我尝试克隆数据库以运行测试时,我得到一个明显的错误。 我在这里做错了吗? 我应该在rails中使用其他一些约定吗?

谢谢您的帮助。

由于MySQL索引已经不区分大小写,我猜你正在处理PostgreSQL,默认情况下会创建区分大小写的索引。 我在这里基于Rails 3.2.3和PostgreSQL 8.4回答。

似乎function索引是ActiveRecord无法生成的事物的又一个例子。 外键和UUID列是另外两个想到的。 所以没有选择(除了猴子修补ActiveRecord),而是使用execute语句。

这意味着为了准确转储数据库,您需要放弃与数据库无关的schema.rb,转而使用特定于DB的structure.sql。 请参阅“迁移Rails指南”,第6.2节“架构转储类型” 。 设置如下:

配置/ application.rb中

 config.active_record.schema_format = :sql 

运行迁移时,应自动更新db / structure.sql。 您可以使用以下命令手动生成它:

 rake db:structure:dump 

该文件是纯Postgres SQL。 虽然在使用rake -T列出rake任务时没有记录,但似乎可以使用此命令从structure.sql转储加载数据库:

 rake db:structure:load 

这里没有什么神奇之处: 源代码 (在Rails 3.2.16中显示)只是在structure.sql上调用psql。

最后,我的迁移是删除旧的,区分大小写的电子邮件约束并添加区分大小写的function索引:

 class FixEmailUniqueIndexOnUsers < ActiveRecord::Migration def up remove_index :users, :email execute "CREATE UNIQUE INDEX index_users_on_lowercase_email ON users USING btree (lower(email));" end def down execute "DROP INDEX index_users_on_lowercase_email;" add_index :users, :email, :unique => true end end 

如果您使用的是PostgreSQL,则可以将列类型更改为citext – 不区分大小写的字符串。 它还使搜索独立于寄存器。

 def change enable_extension :citext change_column :users, :email, :citext add_index :users, :email, unique: true end 

我会简化这个……

在你的模型中:

 before_validation :downcase_email def downcase_email self.email = email.downcase end 

这样,索引与数据库无关,并且您的电子邮件在数据库中都是小写的。

您是否考虑过使用schema_plushttps://github.com/lomba/schema_plus )? 除了支持在数据库和视图中强制执行外键之外,它还支持为PostgreSQL数据库设置不区分大小写的索引,并处理它们在模式中的转储。 在自述文件中,“如果您正在使用Postgresql,则SchemaPlus提供对条件,表达式,索引方法和不区分大小写的索引的支持。”

文档不清楚如何执行此操作,但此源代码如下所示:

 def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})" end 

因此,如果您的数据库的quote_column_name是默认实现(它什么都不做),那么这可能会起作用:

 add_index "users", ['lower(email)'], :name => "index_users_on_lower_email_index", :unique => true 

你注意到你试过那个,但它不起作用(将它添加到你的问题可能是一个好主意)。 看起来ActiveRecord根本不了解计算值的索引。 我可以想到一个丑陋的黑客会完成它,但它很难看:

  1. 添加email_lc列。
  2. 添加before_validationbefore_save挂钩,将小写版本的email放入email_lc
  3. 将您的唯一索引放在email_lc

这很丑陋,你可能觉得这样做很脏,但这是我现在能想到的最好的。

我建议(只是一个考虑其他人的机会)使用两个单独的字段:

  • 电子邮件
  • email_original

第一个始终是向下的,因此可以以数据库无关的方式进行唯一索引,而第二个字段保持逐字记录用户输入的内容并且可以具有高位字符。

显然,在用户模型中,需要在每次保存时根据email_original设置电子邮件,并禁止使用电子邮件字段直接修改。

对于Rails 4.2,在name列的users表中创建不区分大小写的唯一索引。

使用空更改方法创建新的迁移文件:

 $ rails generate migration add_index_in_users_on_name 

添加add_index方法的调用以清空更改方法:

 add_index :users, 'lower(name)', name: 'index_users_on_lower_name', unique: true 

运行Rake db:migrate任务:

 $ rake db:migrate 

结果,将正确添加索引,文件db / schema.rb包含正确的add_index:

 add_index "users", ["LOWER(\"NAME\")"], name: "index_users_on_lower_name", unique: true 

这只测试了RDB Oracle。

我认为你需要列名如下

  add_index "users", [email], {:name => "index_users_on_lower_email_index", :unique => true } 

并且您必须使用正确的Case Insensitive排序规则在数据库中创建电子邮件字段,这样您的索引也将不区分大小写。

根据您使用的数据库引擎,语法可能不同但是

 alter table [users] alter column [email] varchar(250) collate utf8_general_ci ... 

当您向此列添加索引时,它将不区分大小写。