在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 :)