我的产品型号包含一些商品
Product.first
=> #<Product id: 10, name: "Blue jeans" >
我现在从另一个数据集导入一些产品参数,但名称的拼写有不一致之处。例如,在其他数据集中,Blue jeans
可以拼写为Blue Jeans
。
我想要Product.find_or_create_by_name("Blue Jeans")
,但这会创建一个新产品,几乎与第一个完全相同。如果我想找到并比较小写的名字,我有什么选择。
性能问题在这里并不重要:只有100-200个产品,我希望将其作为导入数据的迁移来运行。
有什么想法吗?
答案 0 :(得分:342)
你可能必须在这里更详细
name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first
model ||= Product.create(:name => name)
答案 1 :(得分:97)
这是Rails中的完整设置,供我自己参考。如果它对你有帮助我很高兴。
查询:
Product.where("lower(name) = ?", name.downcase).first
验证者:
validates :name, presence: true, uniqueness: {case_sensitive: false}
索引(来自Case-insensitive unique index in Rails/ActiveRecord?的答案):
execute "CREATE UNIQUE INDEX index_products_on_lower_name ON products USING btree (lower(name));"
我希望有一个更好的方式来做第一个和最后一个,但是再一次,Rails和ActiveRecord是开源的,我们不应该抱怨 - 我们可以自己实现它并发送pull请求。
答案 2 :(得分:22)
如果您正在使用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">
答案 3 :(得分:21)
您可能想要使用以下内容:
validates_uniqueness_of :name, :case_sensitive => false
请注意,默认设置为:case_sensitive =&gt;是的,所以如果你没有改变其他方式,你甚至不需要写这个选项。
答案 4 :(得分:13)
在postgres中:
user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
答案 5 :(得分:9)
任何其他角色匹配或 它的低/大写等价物(即 不区分大小写的匹配)
......我不知道。但它有效:
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
,它可能不是非常跨数据库友好,但值得一看?
答案 6 :(得分:8)
有几条评论提到了Arel,未提供示例。
以下是不区分大小写搜索的Arel示例:
Product.where(Product.arel_table[:name].matches('Blue Jeans'))
此类解决方案的优点是它与数据库无关 - 它将为您当前的适配器使用正确的SQL命令(matches
将ILIKE
用于Postgres,{{1}其他一切)。
答案 7 :(得分:6)
大写和小写字母仅相差一位。搜索它们的最有效方法是忽略此位,而不是转换为低位或高位等。请参阅MSSQL的关键字COLLATION
,如果使用Oracle,请参阅NLS_SORT=BINARY_CI
等。
答案 8 :(得分:5)
没有人提到的另一种方法是在ActiveRecord :: Base中添加不区分大小写的查找程序。可以找到详细信息here。这种方法的优点是您不必修改每个模型,并且您不必将lower()
子句添加到所有不区分大小写的查询中,而只需使用不同的finder方法。
答案 9 :(得分:4)
现在不推荐使用Find_or_create,您应该使用AR Relation而不是first_or_create,如下所示:
TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)
这将返回第一个匹配的对象,或者如果不存在则为您创建一个。
答案 10 :(得分:2)
Rails内置了不区分大小写的搜索功能。它解释了数据库实现的差异。使用the built-in Arel library, or a gem like Squeel。
答案 11 :(得分:2)
这里有很多很棒的答案,特别是@ oma's。但您可以尝试的另一件事是使用自定义列序列化。如果您不介意在数据库中存储小写的所有内容,那么您可以创建:
# 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) = ?
。
缺点是您丢失了数据库中的套管信息。
答案 12 :(得分:1)
你也可以使用下面这样的范围并将它们放在一个关注点中并包含在你可能需要它们的模型中:
scope :ci_find, lambda { |column, value| where("lower(#{column}) = ?", value.downcase).first }
然后像这样使用:
Model.ci_find('column', 'value')
答案 13 :(得分:0)
可以选择
c = Product.find_by("LOWER(name)= ?", name.downcase)
答案 14 :(得分:0)
类似于安德鲁斯,是第一名:
对我有用的是:
name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)
这样就无需在同一查询中执行#where
和#first
。希望这会有所帮助!
答案 15 :(得分:0)
有些人使用LIKE或ILIKE显示,但那些允许正则表达式搜索。你也不需要在Ruby中使用downcase。您可以让数据库为您执行此操作。我认为它可能会更快。 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">
答案 16 :(得分:0)
user = Product.where(email: /^#{email}$/i).first
答案 17 :(得分:0)
假设您使用mysql,您可以使用不区分大小写的字段:http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
答案 18 :(得分:-9)
到目前为止,我使用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 :)