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

时间:2011-10-30 22:55:07

标签: ruby-on-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中使用其他约定?

感谢您的帮助。

8 个答案:

答案 0 :(得分:35)

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

似乎functional indexes是ActiveRecord无法生成的事物的又一个例子。外键和UUID列是另外两个想到的。因此,除了使用猴子修补ActiveRecord之外别无选择,只能使用execute语句。

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

<强>配置/ application.rb中

config.active_record.schema_format = :sql

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

rake db:structure:dump

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

rake db:structure:load

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

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

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

答案 1 :(得分:24)

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

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

答案 2 :(得分:6)

我会简化这个......

在你的模特中:

before_validation :downcase_email

def downcase_email
  self.email = email.downcase
end

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

答案 3 :(得分:4)

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

答案 4 :(得分:2)

documentation目前尚不清楚如何做到这一点,但来源如下:

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
  4. 这很丑陋,你可能觉得这样做很脏,但这是我现在能想到的最好的。

答案 5 :(得分:0)

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

  • 电子邮件
  • email_original

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

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

答案 6 :(得分:-1)

对于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进行了测试。

答案 7 :(得分:-2)

我认为你需要列名如下

   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 ...

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