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