【发布时间】:2011-12-04 13:15:43
【问题描述】:
假设 Rails 3 使用 MySQL DB 和不区分大小写的排序规则
故事是什么:
Rails 允许您使用“唯一性”验证器来验证模型的属性。但是根据 Rails 文档,默认比较是区分大小写的。
这意味着在验证时它会执行如下 SQL:
SELECT 1 FROM `Users` WHERE (`Users`.`email` = BINARY 'FOO@email.com') LIMIT 1
对于拥有 CI Collation 数据库的我来说,这完全是错误的。它会认为 'FOO@email.com' 有效,即使用户表中已经有另一个用户 'foo@email.com'。换句话说,这意味着,如果应用程序的用户尝试使用电子邮件“FOO@email.com”创建一个新用户,这对于 Rails 来说将是完全有效的(默认情况下),并且 INSERT 将被发送到 db。如果您碰巧在电子邮件上没有唯一索引,那么您会大吃一惊 - 将毫无问题地插入行。如果你碰巧有一个唯一索引,那么就会抛出异常。
好的。 Rails 说:由于您的数据库具有不区分大小写的排序规则,因此请执行不区分大小写的唯一性验证。 这是怎么做到的?它告诉您可以通过在特定属性验证器上设置 ":case_sensitive => false" 来覆盖默认的唯一性比较敏感度。在验证时,它会创建以下 SQL:
SELECT 1 FROM `Users` WHERE (LOWER(`Users`.`email`) = LOWER('FOO@email.com') LIMIT 1
这是数据库表用户的灾难,您设计为在电子邮件字段上具有唯一索引,因为它不使用索引,所以会进行全表扫描。
我现在看到SQL中的LOWER函数是由ActiveRecord的UniquenessValidator插入的(文件uniqueness.rb,模块ActiveRecord,模块Validations类UniquenessValidator)。这是执行此操作的一段代码:
if value.nil? || (options[:case_sensitive] || !column.text?)
sql = "#{sql_attribute} #{operator}"
else
sql = "LOWER(#{sql_attribute}) = LOWER(?)"
end
所以问题转到 Rails/ActiveRecord 而不是 MySQL 适配器。
问题:有没有办法告诉 Rails 将有关唯一性验证大小写敏感性的要求传递给 MySQL 适配器,而不是“聪明”地改变查询?要么 为了澄清而改述的问题:是否有另一种方法可以对属性进行唯一性验证(请注意...我不是只谈论电子邮件,以电子邮件为例)关闭区分大小写并生成将在相应列上使用简单唯一索引的查询?
这两个问题是等价的。我希望现在,我让自己更清楚,以便得到更准确的答案。
【问题讨论】:
-
你在 rails-core 小组发过这个吗?他们在那里说什么?
标签: mysql ruby-on-rails