p.m*_*los 7 mysql ruby-on-rails
使用具有Case Insensitive排序规则的MySQL DB假设Rails 3
发生了什么:
Rails允许您使用"唯一性"验证器验证模型的属性.但根据Rails文档,默认比较是CASE SENSITIVE.
这意味着在验证时它会执行如下SQL:
SELECT 1 FROM `Users` WHERE (`Users`.`email` = BINARY 'FOO@email.com') LIMIT 1
Run Code Online (Sandbox Code Playgroud)
对于拥有CI Collation数据库的我来说,这是完全错误的.它会认为'FOO@email.com'有效,即使在Users表中已有另一个用户'foo@email.com'的用户也是如此.换句话说,这意味着,如果应用程序的用户尝试使用电子邮件"FOO@email.com"创建新用户,则对于Rails,这将完全是VALID(默认情况下),并且INSERT将被发送到db.如果你没有碰巧在电子邮件上有唯一的索引,那么你很兴奋 - 插入行将没有问题.如果您碰巧拥有唯一索引,则会抛出异常.
好.Rails说:由于您的数据库具有不区分大小写的排序规则,因此请执行不区分大小写的唯一性验证.这是怎么做到的?它告诉您可以通过在特定属性验证器上设置":case_sensitive => false"来覆盖默认唯一性比较灵敏度.在验证时,它会创建以下SQL:
SELECT 1 FROM `Users` WHERE (LOWER(`Users`.`email`) = LOWER('FOO@email.com') LIMIT 1
Run Code Online (Sandbox Code Playgroud)
这是数据库表上的DISASTER您设计为在电子邮件字段上具有唯一索引的用户,因为它不使用索引,所以执行全表扫描.
我现在看到的是,LOWERSQL函数是由插入UniquenessValidator的ActiveRecord(文件uniqueness.rb,模块ActiveRecord,模块Validations级UniquenessValidator).以下是执行此操作的代码段:
if value.nil? || (options[:case_sensitive] || !column.text?)
sql = "#{sql_attribute} #{operator}"
else
sql = "LOWER(#{sql_attribute}) = LOWER(?)"
end
Run Code Online (Sandbox Code Playgroud)
所以问题转到Rails/ActiveRecord而不是MySQL Adapter.
问题:有没有办法告诉Rails传递关于MySQL适配器的唯一性验证区分大小写的要求,而不是"聪明"地改变查询?或者 为了澄清而重复的问题:是否有另一种方法可以对属性实现唯一性验证(请小心,我只是不谈电子邮件,电子邮件就是一个例子),区分大小写和生成在相应列上使用简单唯一索引的查询?
这两个问题是等价的.我希望现在,我能让自己更清楚,以便获得更准确的答案.
我已经进行了搜索,根据我今天的了解,唯一可以接受的答案是创建一个执行正确查询和检查的验证方法。换句话说,停止使用:uniqueness => true并执行如下操作:
class User
validate :email_uniqueness
protected
def email_uniqueness
entries = User.where('email = ?', email)
if entries.count >= 2 || entries.count == 1 && (new_record? || entries.first.id != self.id )
errors[:email] << _('already taken')
end
end
end
Run Code Online (Sandbox Code Playgroud)
这肯定会使用我的索引email,并且在创建和更新时都有效(或者至少在我测试过的情况下是这样)。
在 RubyOnRails Core Google 小组询问后
我从 RubyOnRails Core Google Group 获取了以下答案:Rails 正在 3.2 上修复此问题。阅读此内容: https://github.com/rails/rails/commit/c90e5ce779dbf9bd0ee53b68aee9fde2997be123