Rails模型中不区分大小写的搜索

Jes*_*sen 206 activerecord ruby-on-rails case-insensitive

我的产品型号包含一些商品

 Product.first
 => #<Product id: 10, name: "Blue jeans" >
Run Code Online (Sandbox Code Playgroud)

我现在从另一个数据集导入一些产品参数,但名称的拼写有不一致之处.例如,在其他数据集中,Blue jeans可以拼写Blue Jeans.

我想Product.find_or_create_by_name("Blue Jeans"),但这将创造一个新产品,几乎与第一个相同.如果我想找到并比较小写的名字,我有什么选择.

性能问题在这里并不重要:只有100-200个产品,我想将其作为导入数据的迁移来运行.

有任何想法吗?

ale*_*dev 354

你可能必须在这里更加冗长

name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first 
model ||= Product.create(:name => name)
Run Code Online (Sandbox Code Playgroud)

  • 在Rails 4中,您现在可以执行`model = Product.where('lower(name)=?',name.downcase).first_or_create` (17认同)
  • @ botbot的评论不适用于用户输入的字符串."#$$"是使用Ruby字符串插值转义全局变量的一个鲜为人知的快捷方式.它相当于"#{$$}".但字符串插值不会发生在用户输入的字符串中.在Irb中尝试这些以查看区别:`"$ ##"`和`'$ ##'`.第一个是插值(双引号).第二个不是.用户输入永远不会被插值. (5认同)
  • 只是要注意`find(:first)`已被弃用,现在的选择是使用`#first`.因此,`Product.first(conditions:["lower(name)=?",name.downcase])` (5认同)
  • 你不需要做所有这些工作.使用[内置Arel库或Squeel](http://robb.weblaws.org/2013/12/05/yes-rails-supports-case-insensitive-database-queries/) (2认同)

oma*_*oma 99

这是Rails中的完整设置,供我自己参考.如果它对你有帮助我很高兴.

查询:

Product.where("lower(name) = ?", name.downcase).first
Run Code Online (Sandbox Code Playgroud)

验证者:

validates :name, presence: true, uniqueness: {case_sensitive: false}
Run Code Online (Sandbox Code Playgroud)

索引(从Rails/ActiveRecord中的Case-insensitive唯一索引回答):

execute "CREATE UNIQUE INDEX index_products_on_lower_name ON products USING btree (lower(name));"
Run Code Online (Sandbox Code Playgroud)

我希望有一个更美好的方式来做第一个和最后一个,但是再一次,Rails和ActiveRecord是开源的,我们不应该抱怨 - 我们可以自己实现它并发送pull请求.

  • 感谢您在PostgreSQL中创建不区分大小写的索引.感谢您展示如何在Rails中使用它!另外一个注意事项:如果您使用标准查找程序,例如find_by_name,它仍然会完全匹配.如果您希望搜索不区分大小写,则必须编写自定义查找程序,类似于上面的"查询"行. (6认同)

Vie*_*iet 26

如果您使用的是Postegres和Rails 4+,那么您可以选择使用列类型CITEXT,这将允许不区分大小写的查询而无需写出查询逻辑.

迁移:

def change
  enable_extension :citext
  change_column :products, :name, :citext
  add_index :products, :name, unique: true # If you want to index the product names
end
Run Code Online (Sandbox Code Playgroud)

要测试它你应该期望以下:

Product.create! name: 'jOgGers'
=> #<Product id: 1, name: "jOgGers">

Product.find_by(name: 'joggers')
=> #<Product id: 1, name: "jOgGers">

Product.find_by(name: 'JOGGERS')
=> #<Product id: 1, name: "jOgGers">
Run Code Online (Sandbox Code Playgroud)


Soh*_*han 21

您可能想要使用以下内容:

validates_uniqueness_of :name, :case_sensitive => false
Run Code Online (Sandbox Code Playgroud)

请注意,默认设置为:case_sensitive => false,因此如果您没有更改其他方式,则甚至不需要编写此选项.

有关更多信息,请访问:http: //api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

  • 根据我的经验,与文档相比,case_sensitive默认为true.我已经看到postgresql和其他人的行为在mysql中报告相同. (5认同)

tom*_*nek 13

在postgres:

 user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
Run Code Online (Sandbox Code Playgroud)


Bra*_*rth 10

有几条评论提到了Arel,没有提供一个例子.

以下是不区分大小写搜索的Arel示例:

Product.where(Product.arel_table[:name].matches('Blue Jeans'))
Run Code Online (Sandbox Code Playgroud)

这种解决方案的优点是它与数据库无关 - 它将为您当前的适配器使用正确的SQL命令(matchesILIKE用于Postgres,以及LIKE其他所有内容).

  • 确保正确处理`_`、`%`以及是否有任何转义字符。在MySQL中默认转义符是“\”,但在oracle中没有默认转义符,您需要将其作为第二个参数添加到“#matches”中。 (2认同)
  • 另一个问题是 Oracle 不支持不区分大小写的搜索。我正在使用 `UPPER()` 准备一个补丁,稍后将提交给 oracle 增强适配器。 (2认同)

小智 10

类似于排名第一的安德鲁斯:

对我有用的是:

name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)
Run Code Online (Sandbox Code Playgroud)

这消除了在同一查询中执行#whereand的需要。#first希望这可以帮助!


Mik*_*use 9

引用SQLite文档:

任何其他字符匹配自身或其低/大写等效(即不区分大小写的匹配)

......我不知道.但它有效:

sqlite> create table products (name string);
sqlite> insert into products values ("Blue jeans");
sqlite> select * from products where name = 'Blue Jeans';
sqlite> select * from products where name like 'Blue Jeans';
Blue jeans
Run Code Online (Sandbox Code Playgroud)

所以你可以这样做:

name = 'Blue jeans'
if prod = Product.find(:conditions => ['name LIKE ?', name])
    # update product or whatever
else
    prod = Product.create(:name => name)
end
Run Code Online (Sandbox Code Playgroud)

#find_or_create,我知道,它可能不是非常跨数据库友好,但值得一看?


小智 7

另一种选择可以是

c = Product.find_by("LOWER(name)= ?", name.downcase)
Run Code Online (Sandbox Code Playgroud)


Dea*_*ffe 6

大写和小写字母只有一位不同 - 搜索它们的最有效方法是忽略这一位,而不是转换为低位或高位等.请参阅关键字COLLATION for MS SQL,如果使用Oracle,请参阅NLS_SORT = BINARY_CI,等等..


Ale*_*ban 5

没有人提到的另一种方法是将不区分大小写的查找器添加到ActiveRecord :: Base中.细节可以在这里找到.这种方法的优点是您不必修改每个模型,也不lower()必将子句添加到所有不区分大小写的查询中,而只需使用不同的finder方法.

  • @ XP84我不知道这有多重要,但是我已经修复了链接。 (3认同)
  • 正如@Anthony所预言的那样,它已经实现了。链接死了。 (2认同)

sup*_*ary 5

Find_or_create 现在已弃用,您应该使用 AR Relation 代替加上 first_or_create,如下所示:

TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)
Run Code Online (Sandbox Code Playgroud)

这将返回第一个匹配的对象,或者如果不存在则为您创建一个。