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

Ruby on rails Rails模型中不区分大小写的搜索,ruby-on-rails,activerecord,case-insensitive,Ruby On Rails,Activerecord,Case Insensitive,我的产品模型包含一些项目 Product.first => #<Product id: 10, name: "Blue jeans" > Product.first => # 我现在从另一个数据集中导入一些产品参数,但是名称的拼写不一致。例如,在另一个数据集中,蓝色牛仔裤可以拼写为蓝色牛仔裤 我想要产品。查找\u或按\u名称创建\u(“蓝色牛仔裤”),但这将创建一个新产品,几乎与第一个相同。如果我想查找和比较小写名称,我的选项是什么 性能问题在这里并不重要:只有100-

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

 Product.first
 => #<Product id: 10, name: "Blue jeans" >
Product.first
=> #
我现在从另一个数据集中导入一些产品参数,但是名称的拼写不一致。例如,在另一个数据集中,
蓝色牛仔裤
可以拼写为
蓝色牛仔裤

我想要
产品。查找\u或按\u名称创建\u(“蓝色牛仔裤”)
,但这将创建一个新产品,几乎与第一个相同。如果我想查找和比较小写名称,我的选项是什么

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


有什么想法吗?

你可能需要在这里说得更详细些

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

假设您使用mysql,您可以使用不区分大小写的字段:

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

  #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 :)
>Product.create(:name=>“蓝色牛仔裤”)
=> #
>>产品。定制按名称查找(“蓝色牛仔裤”)
=>零
>>Product.flush\u自定义\u查找器\u缓存!
=>零
>>产品。定制按名称查找(“蓝色牛仔裤”)
=> #
>>
>>成功!我发现你:)
引用以下内容:

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

…我不知道。但它是有效的:

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

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

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

validates_uniqueness_of :name, :case_sensitive => false
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">
请注意,默认设置为:区分大小写=>false,因此如果没有更改其他方式,您甚至不需要编写此选项

有关详细信息,请访问:
另一种没有人提到的方法是将不区分大小写的查找程序添加到ActiveRecord::Base中。详情请参阅。这种方法的优点是,您不必修改每个模型,也不必将
lower()
子句添加到所有不区分大小写的查询中,只需使用不同的查找器方法即可。

大小写字母仅相差一位。搜索它们的最有效方法是忽略此位,而不是转换低位或高位等。请参阅MSSQL的关键字
排序规则
,如果使用Oracle,请参阅
NLS\u SORT=BINARY\u CI
,等等。

在postgres中:

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

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

查询:

Product.where("lower(name) = ?", name.downcase).first
验证器:

validates :name, presence: true, uniqueness: {case_sensitive: false}
索引(来自的答案):


我希望有一种更漂亮的方法来完成第一步和最后一步,但是再说一次,Rails和ActiveRecord是开源的,我们不应该抱怨——我们可以自己实现它并发送pull请求

现在不推荐使用Find_或create,您应该使用AR关系加上first_或create,如下所示:

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

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

Rails内置了不区分大小写的搜索功能。它解释了数据库实现的差异。任何一个都可以使用。

这里有很多很好的答案,尤其是@oma。但您可以尝试的另一件事是使用自定义列序列化。如果您不介意将所有内容以小写形式存储在数据库中,那么您可以创建:

# 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\u或\u create\u by
),而无需在查询中使用自定义范围、函数或
lower(name)=?


缺点是,您会丢失数据库中的大小写信息。

有些人使用LIKE或ILIKE显示,但这些允许正则表达式搜索。此外,您不需要在Ruby中使用downcase。你可以让数据库为你做这件事。我想可能更快。也可以在
where
之后使用
first\u或\u create

# 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"> 
#app/models/product.rb
类产品 # 

如果使用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
要测试它,您应该预期以下内容:

validates_uniqueness_of :name, :case_sensitive => false
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">
Product.create!姓名:“慢跑者”
=> #
产品。查找方式(名称:“慢跑者”)
=> #
产品。查找方式(名称:“慢跑者”)
=> #

您也可以使用下面这样的作用域,将它们放在关注点中,并包含在您可能需要的模型中:

scope:ci_find,lambda{|列,value |其中(“lower({column})=?”,value.downcase)。first}

然后像这样使用:
Model.ci\u find('column','value')
一些注释引用了Arel,但没有提供示例

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

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

这种类型的解决方案的优点是它与数据库无关-它将为当前适配器使用正确的SQL命令(
matches
将对Postgres使用
ILIKE
,对其他所有内容使用
LIKE

类似于安德鲁斯的#1:

对我有用的是:

name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)
这样就无需在同一查询中执行
#where
#first
。希望这有帮助

另一种选择是

c = Product.find_by("LOWER(name)= ?", name.downcase)

like在mysql中区分大小写,但在postgresql中不区分大小写。我不确定Oracle或DB2。关键是,你不能指望它,如果你使用它,而你的老板改变了你的底层数据库,你就会开始“丢失”记录,而没有明显的原因@中微子的较低(名称)建议是