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

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

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

我想要产品。find_or_create_by_name("Blue Jeans"),但这将创建一个与第一个几乎相同的新产品。我的选择是什么,如果我想找到和比较小写的名字。

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

什么好主意吗?


这里你可能得更啰嗦一些

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

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


到目前为止,我使用Ruby编写了一个解决方案。把它放在Product模型中:

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

引用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,我知道,它可能不是很跨数据库友好,但值得一看?


你可能想要使用以下语句:

validates_uniqueness_of :name, :case_sensitive => false

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

欲知详情,请浏览: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of


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


大写字母和小写字母只差一位。最有效的搜索方法是忽略这个位,而不是转换上下位等等。如果使用Oracle,请参见NLS_SORT=BINARY_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中不区分大小写的唯一索引?):

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

我希望有一种更漂亮的方式来完成第一个和最后一个,但话又说回来,Rails和ActiveRecord是开源的,我们不应该抱怨——我们可以自己实现它并发送pull request。


Find_or_create现在已弃用,你应该使用一个AR关系来代替加上first_or_create,如下所示:

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

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


不区分大小写的搜索是Rails内置的。它解释了数据库实现的差异。使用内置的Arel库,或者像Squeel这样的宝石。


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

这里有很多很棒的答案,尤其是@oma的。但是您还可以尝试使用自定义列序列化。如果你不介意在你的db中存储小写字母,那么你可以创建:

# 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),而无需使用自定义作用域、函数或使用较低的(name) = ?在你的查询中。

缺点是会丢失数据库中的套管信息。


有些人使用LIKE或ILIKE显示,但这些允许正则表达式搜索。在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"> 

如果您正在使用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

为了测试它,你应该期待以下内容:

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

你也可以像下面这样使用作用域,把它们放在一个关注点中,并包括在你可能需要的模型中:

作用域:ci_find, lambda{|列,值| where("lower(#{column}) = ?", value.downcase)。第一}

然后像这样使用: 模型。ci_find(“列”、“价值”)


有几个注释提到了Arel,但没有提供示例。

下面是一个不区分大小写搜索的Arel示例:

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

这种类型的解决方案的优点是它与数据库无关——它将为当前适配器使用正确的SQL命令(匹配将对Postgres使用ILIKE,而对其他一切使用LIKE)。


类似于安德鲁斯的第一条:

对我有用的是:

name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)

这样就不需要在同一个查询中执行#where和#first。希望这能有所帮助!


另一种选择是

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

你可以在模型中这样使用

scope :matching, lambda { |search, *cols|
    where cols.flatten.map{|col| User.arel_table[col].matches("%#{search}%") }.inject(:or)
}

用在你喜欢的任何地方

User.matching(params[:search], :mobile_number, :name, :email)

可以通过多个列进行搜索

对于单列搜索,您可以使用这样的方法

User.where(User.arel_table[:column].matches("%#{search}%"))

如果您正在使用postgres(可能是其他的),我喜欢这个解决方案。

Product.find_by("name ilike 'bLue JEaNS'")

我更喜欢这个,有几个原因。

更清晰的连接到数据库操作->你可以复制粘贴到哪里… 如果您选择添加一个外接符%,这很简单。