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_plus
( https://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根本不了解计算值的索引。 我可以想到一个丑陋的黑客会完成它,但它很难看:
- 添加
email_lc
列。 - 添加
before_validation
或before_save
挂钩,将小写版本的email
放入email_lc
。 - 将您的唯一索引放在
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 ...
当您向此列添加索引时,它将不区分大小写。