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

发布于 2024-08-20 23:58:53 字数 402 浏览 3 评论 0原文

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

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

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

我想要 Product.find_or_create_by_name("Blue Jeans"),但这将创建一个新产品,几乎与第一个产品相同。如果我想查找并比较小写名称,我有什么选择?

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

有什么想法吗?

My product model contains some items

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

I'm now importing some product parameters from another dataset, but there are inconsistencies in the spelling of the names. For instance, in the other dataset, Blue jeans could be spelled Blue Jeans.

I wanted to Product.find_or_create_by_name("Blue Jeans"), but this will create a new product, almost identical to the first. What are my options if I want to find and compare the lowercased name.

Performance issues is not really important here: There are only 100-200 products, and I want to run this as a migration that imports the data.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(21

野稚 2024-08-27 23:58:53

你可能需要在这里更详细

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

You'll probably have to be more verbose here

name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first 
model ||= Product.create(:name => name)
刘备忘录 2024-08-27 23:58:53

这是 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 是开源的,我们不应该抱怨 - 我们可以自己实现它并发送拉取请求。

This is a complete setup in Rails, for my own reference. I'm happy if it helps you too.

the query:

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

the validator:

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

the index (answer from Case-insensitive unique index in Rails/ActiveRecord?):

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

I wish there was a more beautiful way to do the first and the last, but then again, Rails and ActiveRecord is open source, we shouldn't complain - we can implement it ourselves and send pull request.

锦爱 2024-08-27 23:58:53

如果您使用的是 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">

If you are using Postegres and Rails 4+, then you have the option of using column type CITEXT, which will allow case insensitive queries without having to write out the query logic.

The migration:

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

And to test it out you should expect the following:

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">
断肠人 2024-08-27 23:58:53

有几条评论提到了 Arel,但没有提供示例。

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

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

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

Several comments refer to Arel, without providing an example.

Here is an Arel example of a case-insensitive search:

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

The advantage of this type of solution is that it is database-agnostic - it will use the correct SQL commands for your current adapter (matches will use ILIKE for Postgres, and LIKE for everything else).

翻了热茶 2024-08-27 23:58:53

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

validates_uniqueness_of :name, :case_sensitive => false

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

欲了解更多信息,请访问:
http://api.rubyonrails.org/classes/ActiveRecord /Validations/ClassMethods.html#method-i-validates_uniqueness_of

You might want to use the following:

validates_uniqueness_of :name, :case_sensitive => false

Please note that by default the setting is :case_sensitive => false, so you don't even need to write this option if you haven't changed other ways.

Find more at:
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

烏雲後面有陽光 2024-08-27 23:58:53

在 postgres 中:

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

In postgres:

 user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
抱着落日 2024-08-27 23:58:53

与安德鲁斯类似,#1:

对我有用的是:

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

这消除了在同一查询中执行 #where#first 的需要。希望这有帮助!

Similar to Andrews which is #1:

Something that worked for me is:

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

This eliminates the need to do a #where and #first in the same query. Hope this helps!

绅士风度i 2024-08-27 23:58:53

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

Quoting from the SQLite documentation:

Any other character matches itself or
its lower/upper case equivalent (i.e.
case-insensitive matching)

...which I didn't know.But it works:

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

So you could do something like this:

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

Not #find_or_create, I know, and it may not be very cross-database friendly, but worth looking at?

玻璃人 2024-08-27 23:58:53

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

Another approach that no one has mentioned is to add case insensitive finders into ActiveRecord::Base. Details can be found here. The advantage of this approach is that you don't have to modify every model, and you don't have to add the lower() clause to all your case insensitive queries, you just use a different finder method instead.

扶醉桌前 2024-08-27 23:58:53

另一种选择可以是

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

An alternative can be

c = Product.find_by("LOWER(name)= ?", name.downcase)
红玫瑰 2024-08-27 23:58:53

大小写字母仅相差一位。搜索它们的最有效方法是忽略此位,而不是转换下限或上限等。对于 MSSQL,请参阅关键字 COLLATION,如果使用 Oracle,请参阅 NLS_SORT=BINARY_CI, ETC。

Upper and lower case letters differ only by a single bit. The most efficient way to search them is to ignore this bit, not to convert lower or upper, etc. See keywords COLLATION for MSSQL, see NLS_SORT=BINARY_CI if using Oracle, etc.

☆獨立☆ 2024-08-27 23:58:53

Find_or_create 现已弃用,您应该使用 AR 关系加上first_or_create,如下所示:

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

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

Find_or_create is now deprecated, you should use an AR Relation instead plus first_or_create, like so:

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

This will return the first matched object, or create one for you if none exists.

新一帅帅 2024-08-27 23:58:53

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

Case-insensitive searching comes built-in with Rails. It accounts for differences in database implementations. Use either the built-in Arel library, or a gem like Squeel.

幻想少年梦 2024-08-27 23:58:53

这里有很多很好的答案,特别是@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_or_create_by),而无需在查询中使用自定义范围、函数或使用 lower(name) = ?

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

There are lots of great answers here, particularly @oma's. But one other thing you could try is to use custom column serialization. If you don't mind everything being stored lowercase in your db then you could create:

# 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

Then in your model:

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

The benefit of this approach is that you can still use all the regular finders (including find_or_create_by) without using custom scopes, functions, or having lower(name) = ? in your queries.

The downside is that you lose casing information in the database.

尝蛊 2024-08-27 23:58:53

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

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

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

  1. 更清晰地连接到数据库操作 ->您只需将其复制粘贴到...
  2. 如果您选择添加通配符 %,这很简单。

If you're using postgres (probably others), I like this solution.

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

I like this better for a couple reasons.

  1. Clearer connection to database action -> you can just copy paste that into where ...
  2. If you choose to add a wildard %, it's straightforward.
花心好男孩 2024-08-27 23:58:53

您还可以使用如下所示的范围,并将它们放在关注点中,并将其包含在您可能需要它们的模型中:

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

然后像这样使用:
Model.ci_find('列', '值')

You can also use scopes like this below and put them in a concern and include in models you may need them:

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

Then use like this:
Model.ci_find('column', 'value')

月下客 2024-08-27 23:58:53

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

Assuming that you use mysql, you could use fields that are not case sensitive: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

仙女 2024-08-27 23:58:53
user = Product.where(email: /^#{email}$/i).first
user = Product.where(email: /^#{email}$/i).first
血之狂魔 2024-08-27 23:58:53

有些人使用 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"> 

Some people show using LIKE or ILIKE, but those allow regex searches. Also you don't need to downcase in Ruby. You can let the database do it for you. I think it may be faster. Also first_or_create can be used after 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"> 
篱下浅笙歌 2024-08-27 23:58:53

您可以在模型中像这样使用

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}%"))

You can use like this in model

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

and use wherever you like this

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

You can pass multiple column for search

for single column search you can use like this

User.where(User.arel_table[:column].matches("%#{search}%"))
睫毛上残留的泪 2024-08-27 23:58:53

到目前为止,我使用 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 :)

So far, I made a solution using Ruby. Place this inside the Product model:

  #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

This will give me the first product where names match. Or nil.

>> 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 :)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文