web-dev-qa-db-ja.com

Railsモデルでの大文字と小文字を区別しない検索

私の製品モデルにはいくつかのアイテムが含まれています

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

現在、別のデータセットからいくつかの製品パラメーターをインポートしていますが、名前のつづりに矛盾があります。たとえば、他のデータセットでは、Blue jeansのスペルはBlue Jeansです。

私はProduct.find_or_create_by_name("Blue Jeans")を望んでいましたが、これは最初の製品とほぼ同じ新しい製品を作成します。小文字の名前を見つけて比較する場合のオプションは何ですか。

ここでは、パフォーマンスの問題はあまり重要ではありません。製品は100〜200のみであり、これをデータをインポートする移行として実行したいと思います。

何か案は?

202

あなたはおそらくここでより冗長にする必要があります

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

これは、私自身が参照するための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はオープンソースです。文句を言うべきではありません。自分で実装してプルリクエストを送信できます。

99
oma

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

次を使用することができます。

validates_uniqueness_of :name, :case_sensitive => false

デフォルトでは、設定は:case_sensitive => falseであるため、他の方法を変更していない場合、このオプションを記述する必要はありません。

詳細については、 http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

21
Sohan

Postgresの場合:

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

いくつかのコメントは、例を提供せずにArelを参照しています。

大文字と小文字を区別しない検索のArelの例を次に示します。

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

このタイプのソリューションの利点は、データベースに依存しないことです。現在のアダプターに正しいSQLコマンドを使用します(matchesはPostgresにILIKEを使用し、すべてにLIKEを使用しますその他)。

9
Brad Werth

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ではない、私は知っているし、データベース間であまり友好的ではないかもしれないが、見る価値はあるのか?

9
Mike Woodhouse

大文字と小文字は1ビットだけ異なります。それらを検索する最も効率的な方法は、下位または上位などを変換せずに、このビットを無視することです。MSSQLのキーワードCOLLATION、Oracleを使用する場合はNLS_SORT=BINARY_CIを参照してください。

6
Dean Radcliffe

誰も言及していない別のアプローチは、大文字と小文字を区別しないファインダーをActiveRecord :: Baseに追加することです。詳細は こちら をご覧ください。このアプローチの利点は、すべてのモデルを変更する必要がなく、すべての大文字と小文字を区別しないクエリにlower()句を追加する必要がないことです。代わりに異なるFinderメソッドを使用します。

5
Alex Korban

Find_or_createは非推奨になりました。代わりに、ARリレーションとfirst_or_createを使用する必要があります。

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

これは、最初に一致したオブジェクトを返すか、存在しない場合は作成します。

4
superluminary

ここには、特に@omaの素晴らしい答えがたくさんあります。しかし、もう1つ試すことができるのは、カスタム列のシリアル化を使用することです。すべてを小文字でデータベースに保存することを気にしない場合は、次を作成できます。

# 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

このアプローチの利点は、カスタムスコープ、関数を使用したり、クエリでlower(name) = ?を使用したりせずに、すべての通常のファインダー(find_or_create_byを含む)を使用できることです。

欠点は、データベース内のケーシング情報が失われることです。

2
Nate Murray

大文字と小文字を区別しない検索がRailsに組み込まれています。データベース実装の違いを説明します。 組み込みのArelライブラリ、またはSqueelのようなgem を使用します。

2
Dogweather

また、以下のようなスコープを使用して懸念に入れ、必要なモデルに含めることもできます。

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

次に、次のように使用します:Model.ci_find('column', 'value')

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

Mysqlを使用すると仮定すると、大文字と小文字を区別しないフィールドを使用できます。 http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

0
marcgg

#1のアンドリュースに似ています:

私のために働いたものは次のとおりです:

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

これにより、同じクエリで#where#firstを実行する必要がなくなります。お役に立てれば!

0

代替手段は

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

一部の人々はLIKEまたはILIKEを使用して表示しますが、正規表現検索を許可します。また、Rubyでダウンケースする必要もありません。データベースに任せることができます。もっと速くなると思う。また、first_or_createwhereの後に使用できます。

# 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"> 
0
6ft Dan