【发布时间】:2008-10-30 19:05:48
【问题描述】:
非常简单的问题 - 如何在 ActiveRecord 中搜索名称以某个字符串开头的所有记录。我在互联网上看到过各种使用逐字 LIKE SQL 子句的地方 - 但据我所知,这不是“正确”的做法。
是否有“正确”的 Rails 方式?
【问题讨论】:
标签: ruby-on-rails ruby activerecord
非常简单的问题 - 如何在 ActiveRecord 中搜索名称以某个字符串开头的所有记录。我在互联网上看到过各种使用逐字 LIKE SQL 子句的地方 - 但据我所知,这不是“正确”的做法。
是否有“正确”的 Rails 方式?
【问题讨论】:
标签: ruby-on-rails ruby activerecord
如果您要在数据库中进行搜索,则需要使用 SQL。
当然,您需要在数据库中进行搜索,否则您需要将所有对象加载到 Ruby 中(这不是一件好事)。
所以,你需要类似的东西
MyModel.find(:all, :conditions => ["field LIKE ?", "#{prefix}%"])
其中prefix 是一个包含您要查找的字符串的变量。
在 Rails 3.0+ 中,这变成:
MyModel.where("field LIKE :prefix", prefix: "#{prefix}%")
【讨论】:
% 字符注入?
我强烈推荐Searchlogic 插件。
那么就这么简单:
@search = Model.new_search(params[:search])
@search.condition.field_starts_with = "prefix"
@models = @search.all
Searchlogic 很聪明,就像 ActiveRecord 一样,可以在 starts_with 条件中提取字段名称。它还将处理所有分页。
此方法将有助于防止 SQL 注入,并且与数据库无关。 Searchlogic 最终会根据您使用的数据库适配器以不同方式处理搜索。您也不必编写任何 SQL!
Searchlogic 有很好的文档并且易于使用(我自己是 Ruby 和 Rails 的新手)。当我遇到困难时,插件的作者甚至在几个小时内直接回复了一封电子邮件,帮助我解决了我的问题。我不能推荐 Searchlogic 足够多......正如你所知道的那样。
【讨论】:
我不想每次想查看特定列是否以前缀开头时都编写范围。
# initializers/active_record_initializers.rb
class ActiveRecord::Base
# do not accept a column_name from the outside without sanitizing it
# as this can be prone to sql injection
def self.starts_with(column_name, prefix)
where("lower(#{column_name}) like ?", "#{prefix.downcase}%")
end
end
这样称呼它:
User.starts_with('name', 'ab').limit(1)
【讨论】:
ApplicationRecord。
免责声明:我是 Ruby 和 Rails 的新手,我仍在努力学习 Ruby 做事的方式,但我已经有大半辈子的时间在编写代码,而且从事专业工作已经有十年了。 DRY 是一个我非常熟悉的概念。这是我实现它的方式。和narsk的回答很像,我觉得也不错。
# name_searchable.rb
# mix this into your class using
# extend NameSearchable
module NameSearchable
def search_by_prefix (prefix)
self.where("lower(name) LIKE '#{prefix.downcase}%'")
end
end
然后在你的模型中:
class User < ActiveRecord::Base
extend NameSearchable
...
end
然后当你想使用它时:
User.search_by_prefix('John') #or
User.search_by_prefix("#{name_str}")
要说的一件事:
传统的关系数据库在满足这类查询方面并不是非常出色。如果您正在寻找一个不会在负载下杀死您的数据库的高响应实现,您可能应该使用针对该目的量身定制的解决方案。流行的例子包括 Solr 或 Sphinx,还有很多其他的。有了这个实现,因为你 DRY,你可以在一个地方用一个单独的索引器替换这个实现,你就可以开始摇滚了。
【讨论】:
prefix 来自类似搜索表单,我可以在该表单中输入something' OR 1 --。这将列出所有条目。指南中也指出了这一点:guides.rubyonrails.org/…
非常类似于上面的答案,但如果您使用 ActiveRecord...2.1 或更高版本,您可以使用命名范围,允许您在通过关联检索的记录上使用此“查找器”:
class User
named_scope :with_name_like, lambda {|str|
:conditions => ['lower(name) like ?', %(%#{str.downcase}%)]
}
end
像这样使用:
User.with_name_like("Samson")
(返回数据库中所有名称类似于“Samson”的用户。
或者:
some_association.users.with_name_like("Samson")
用户仅使用“Samson”之类的名称离开该关联。
【讨论】:
现在是 2012 年,我想我会在此更新中加入 narsk 的回答。
named_scope 不久前变成了简单的scope 所以例子变成了
class User
scope :name_starts_with, lambda {|str|
:conditions => ['lower(name) like ?', "#{str.downcase}%"]
}
end
请注意,我从 narsk 的解决方案中删除了第一个 %,因为 OP 要求的是“starts_with”而不是“contains”匹配。
现在你可以做类似的事情
User.name_starts_with("b").each do {|bs| puts bs.inspect}
bob = User.name_starts_with("bob").first
… etc
请注意,范围总是返回集合,而不是单个结果。当我第一次开始使用 AR 时,这让我很震惊,但我仍然时不时忘记这一点。
【讨论】:
Dave Sag,我猜你的代码的第一部分应该是
class User
scope :name_starts_with, (lambda do |str|
{:conditions => ['lower(name) like ?', "#{str.downcase}%"]}
end )
end
【讨论】:
同一事物的非常简洁的语法可能是:
Model.where(field: ('prefix'...'prefiy'))
这呈现:
WHERE ( field >= 'prefix' AND field < 'prefiy')
这是因为 'prefiy' 是第一个按字母顺序与 'prefix' 前缀不匹配的字符串。
我不会正常使用它(我会选择 squeel 或 ransack),但如果由于某种原因您必须坚持查询的哈希语法,它可以节省您的时间......
【讨论】: