在Rails模型中不区分大小写的search

我的产品型号包含一些项目

Product.first => #<Product id: 10, name: "Blue jeans" > 

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

我想要Product.find_or_create_by_name("Blue Jeans") ,但这将创造一个新的产品,几乎相同的第一个。 如果我想查找和比较小写的名字,我有什么select。

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

有任何想法吗?

你可能在这里更加冗长

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

这是Rails中的一个完整的设置,供我自己参考。 我很高兴,如果它也可以帮助你。

查询:

 Product.where("lower(name) = ?", name.downcase).first 

validation者:

 validates :name, presence: true, uniqueness: {case_sensitive: false} 

该索引(来自Rails / ActiveRecord中不区分大小写唯一索引的答案):

 execute "CREATE UNIQUE INDEX index_products_on_lower_name ON products USING btree (lower(name));" 

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

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

 validates_uniqueness_of :name, :case_sensitive => false 

请注意,默认情况下,设置是:case_sensitive => false,所以如果你没有改变其他的方法,你甚至不需要写这个选项。

有关详情,请访问: http : //api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

在postgres中:

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

如果您使用的是Postegres和Rails 4+,那么您可以select使用列typesCITEXT,这将允许不区分大小写的查询而不必写出查询逻辑。

迁移:

 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 

要testing它,你应该期望以下几点:

 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"> 

从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 

所以你可以做这样的事情:

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

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

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

另一种没有人提到的方法是在ActiveRecord :: Base中添加不区分大小写的查找器。 详情可以在这里find。 这种方法的优点是你不需要修改每一个模型,你不必为所有不区分大小写的查询添加lower()子句,而只需要使用不同的查找方法。

现在不build议使用Find_or_create,而应该使用AR关系,而不是使用first_or_create,如下所示:

 TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name) 

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

不区分大小写的search使用Rails内置。 它说明了数据库实现的差异。 使用内置的Arel库,或像Squeel那样的gem 。

这里有很多很棒的答案,特别是@ oma的。 但是你可以尝试的另一件事是使用自定义列序列化。 如果你不介意在你的数据库中存储所有的小写字母,那么你可以创build:

 # lib/serializers/downcasing_string_serializer.rb module Serializers class DowncasingStringSerializer def self.load(value) value end def self.dump(value) value.downcase end end end 

然后在你的模型中:

 # app/models/my_model.rb serialize :name, Serializers::DowncasingStringSerializer validates_uniqueness_of :name, :case_sensitive => false 

这种方法的好处是,你仍然可以使用所有的常规发现者(包括find_or_create_by ),而不使用自定义作用域,函数或者具有lower(name) = ? 在您的查询。

缺点是你失去了数据库中的套pipe信息。

有几条评论指的是Arel,没有提供一个例子。

这是一个不区分大小写的search的Arel示例:

 Product.where(Product.arel_table[:name].matches('Blue Jeans')) 

这种types的解决scheme的优点是它是数据库不可知的 – 它将使用正确的SQL命令为您的当前适配器( matches将使用ILIKE Postgres和LIKE的一切)。

假设你使用mysql,你可以使用不区分大小写的字段: http : //dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

 user = Product.where(email: /^#{email}$/i).first 

有些人使用LIKE或ILIKE显示,但那些允许正则expression式search。 你也不需要在Ruby中调用。 你可以让数据库为你做。 我想这可能会更快。 另外first_or_create可以在where

 # app/models/product.rb class Product < ActiveRecord::Base # case insensitive name def self.ci_name(text) where("lower(name) = lower(?)", text) end end # first_or_create can be used after a where clause Product.ci_name("Blue Jeans").first_or_create # Product Load (1.2ms) SELECT "products".* FROM "products" WHERE (lower(name) = lower('Blue Jeans')) ORDER BY "products"."id" ASC LIMIT 1 # => #<Product id: 1, name: "Blue jeans", created_at: "2016-03-27 01:41:45", updated_at: "2016-03-27 01:41:45"> 

您也可以使用下面的示波器,并将它们放在一个问题中,并包含在您可能需要的模型中:

scope :ci_find, lambda { |column, value| where("lower(#{column}) = ?", value.downcase).first }

然后像这样使用: Model.ci_find('column', 'value')

到目前为止,我使用Ruby做了一个解决scheme。 将其放置在产品模型中:

  #return first of matching products (id only to minimize memory consumption) def self.custom_find_by_name(product_name) @@product_names ||= Product.all(:select=>'id, name') @@product_names.select{|p| p.name.downcase == product_name.downcase}.first end #remember a way to flush finder cache in case you run this from console def self.flush_custom_finder_cache! @@product_names = nil end 

这将给我第一个名字匹配的产品。 或者无。

 >> Product.create(:name => "Blue jeans") => #<Product id: 303, name: "Blue jeans"> >> Product.custom_find_by_name("Blue Jeans") => nil >> Product.flush_custom_finder_cache! => nil >> Product.custom_find_by_name("Blue Jeans") => #<Product id: 303, name: "Blue jeans"> >> >> #SUCCESS! I found you :)