Rails Active Record不区分大小写查找

10

虽然一般我喜欢在我的数据库中区分大小写,但有时候确实很麻烦。user_name和email是我不想担心大小写的字段的好例子,特别是在搜索时。

使用类似下面这样的代码,在保存之前将字符串转换为小写非常容易:

before_save do self.email.downcase! end

这样,用户名称和电子邮件始终以小写形式保存。但是,查找find_by_user_name和find_by_email的最佳方法是什么?当然,我可以记住始终将传递到这些方法中的字符串转换为小写,但这似乎不太符合DRY原则。

我考虑过重写find_by_email并在将e-mail转换为小写后调用super,但我没有成功弄清楚。

那么,最好的方法是什么呢?

PS 我不认为像这篇文章(如何编写Rails 3中的不区分大小写的find_by_email)这样的相关帖子试图解决同样的问题。对于我来说,使用“lower”进行自定义SQL是无意义的,因为我已经确保所有这些值都是小写的 - 现在需要将输入的值(可能来自用户输入的表单)转换为小写。


可能是在Rails模型中进行不区分大小写的搜索的重复问题。 - Brad Werth
4个回答

34

使用PostgreSQL,您可以做到:

User.where("email ILIKE ?", email).first

这个是否容易受到 SQL 注入攻击? - GMA
5
为了让未来的读者清楚明白,问题中的版本不会受到SQL注入攻击的威胁。评论中的版本则存在这个风险。 - Kevin
@Kevin 是指哪个版本,Rails?PostgreSQL? - evanrmurphy
@evanrmurphy 看起来我所提到的是一个早期已被删除的评论。 - Kevin
3
Rubocop推荐使用User.find_by 'email ILIKE ?', email,而不是使用where.first,可参考find_by。请注意,我的任务仅限于翻译,不包括解释或提供其他内容。 - Brian Sigafoos

19

以下其中一个应该是好的:

def self.find_by_lower_email(email)
  User.find_by_email(email.downcase)
end
# OR
def self.find_by_email(email)
  User.find(:all, :conditions => ["email = lower(?)", email]) 
end

更新:

实际上,find_by_... 函数并不存在。当你调用其中一个函数时,ActiveRecord::Base 会在 method_missing 方法中捕获调用,如果名称格式正确(包含列名等),则会为该类创建相应的查找方法。之后它将存在。

如果你有一个 find_by_email,默认情况下它会被调用。如果它调用了 super,那么就意味着你尝试调用不存在的 ActiveRecord::Base.find_by_emailmissing_method 会捕获它,并创建(实际上是覆盖)你的 find_by_email 实现。这就是为什么不建议在这里调用 super。如果你使用自己的按邮箱查找实现,就像我在第二部分中写的一样,它将运行得非常出色。


谢谢Matzi,我喜欢你的建议,我会实现第一个。只是好奇,你是否建议创建这个新方法而不是覆盖find_by_email?对我来说,覆盖它似乎更好,因为调用find_by_email很自然--但我不确定如何操作。 - snowguy
在Rails 2.0中存在缺陷,当您调用super时,missing_method会覆盖您的find_by函数,导致它仅能工作一次。我不知道是否已经纠正,但最好安全第一。如果您想调用find_by_email,则使用我的第二个选项,那应该无论如何都可以工作。 - Matzi
抱歉,我没有理解你最后写的那部分内容:“如果你想调用find_by_email,那么使用我的第二个选项...”。在你的第二个选项中,我还是要调用find_by_lower_email吗? - snowguy
我的意思是函数的两个部分是两种不同的实现。如果您调用find_by_email函数,则可以使用第二个部分。 - Matzi
太棒了,感谢您的修订。我明白了。我将实施第二个解决方案。 - snowguy
只需记住第二种方法:find_by_email通常返回一个结果,因此如果您希望其正常工作,则需要在重写方法的数组响应上调用.first。 - Danny

12

我做了类似这样的事情:

def self.find_by_email(email)
  Member.find(:first, :conditions => ["lower(email) =?", email.downcase])
end

你可以就这个问题进行争论,但是一般而言为了避免这种情况发生,电子邮件的格式应该在存入数据库之前进行规范化。

编辑: Rails 3:

Member.where("lower(email) =?", email.downcase).first

要么是我没有理解你的意思,要么是你没有理解我的问题。我同意通常在保存之前应该将字母转换为小写。如果您假设已经完成了这一点,则不需要“lower(email)=?”部分,但仍需要email.downcase部分-因为通常电子邮件地址由用户提供,他们可能没有将其转换为小写-这就是我提问的原因。我认为Matzi的回答是正确的。 - snowguy
你应该知道在插入数据库时它是否被转换为小写。Matzi的回答似乎是一个很好的解决方案,但它假设电子邮件在数据库中已经转换为小写或者在传递到find_by_email方法时已经转换为小写。他的方法也会返回一个电子邮件数组,而:first则会返回第一个响应作为实际的Member对象。不是吗? - rylanb
你对:first提出了一个很好的观点。我认为在解决方案中假设电子邮件是小写的并不是错误的,因为这在问题中特别指出。 - snowguy

0

这对我有用!谢谢,但是使用lower(:xxx)。

Client.includes(:responsibles).where(["lower(name) || lower(contact) || cnpj || clients.rg || rg ILIKE ?", "%#{params[:search]}%"])


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接