web-dev-qa-db-fra.com

Recherche insensible à la casse dans le modèle Rails

Mon modèle de produit contient des articles

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

J'importe maintenant certains paramètres de produit à partir d'un autre jeu de données, mais l'orthographe des noms est incohérente. Par exemple, dans l'autre jeu de données, Blue jeans pourrait être orthographié Blue Jeans.

Je voulais Product.find_or_create_by_name("Blue Jeans"), mais cela va créer un nouveau produit, presque identique au premier. Quelles sont mes options si je veux trouver et comparer le nom en minuscule.

Les problèmes de performances n’ont pas vraiment d’importance ici: il n’ya que 100 à 200 produits, et je veux l’utiliser comme une migration qui importe les données.

Des idées?

202
Jesper Rønn-Jensen

Vous devrez probablement être plus bavard ici

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

Ceci est une configuration complète dans Rails, pour ma propre référence. Je suis content si ça t'aide aussi.

la requête:

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

le validateur:

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

l'index (réponse de index unique insensible à la casse dans Rails/ActiveRecord? ):

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

J'aimerais qu'il y ait un moyen plus beau de faire le premier et le dernier, mais encore une fois, Rails et ActiveRecord sont en open source, nous ne devrions pas nous plaindre - nous pouvons l'implémenter nous-mêmes et envoyer une demande d'extraction.

99
oma

Si vous utilisez Postegres et Rails 4+, vous avez la possibilité d'utiliser le type de colonne CITEXT, ce qui permettra les requêtes ne faisant pas la distinction entre les majuscules et les minuscules sans avoir à écrire la logique de la requête.

La 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

Et pour le tester, vous devez vous attendre à ce qui suit:

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

Vous voudrez peut-être utiliser les éléments suivants:

validates_uniqueness_of :name, :case_sensitive => false

Veuillez noter que le paramètre par défaut est: case_sensitive => false, vous n'avez donc même pas besoin d'écrire cette option si vous n'avez pas changé d'autres méthodes.

Vous trouverez plus d'informations à: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

21
Sohan

En postgres:

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

Plusieurs commentaires font référence à Arel, sans fournir d'exemple.

Voici un exemple Arel de recherche insensible à la casse:

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

L'avantage de ce type de solution est qu'il est indépendant de la base de données. Il utilisera les commandes SQL appropriées pour votre adaptateur actuel (matches utilisera ILIKE pour Postgres et LIKE pour tout. autre).

9
Brad Werth

Citant le documentation SQLite :

Tout autre caractère correspond à lui-même ou à son équivalent majuscule/minuscule (c'est-à-dire la correspondance insensible à la casse)

... que je ne savais pas.Mais ça marche:

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

Donc, vous pourriez faire quelque chose comme ça:

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

Pas #find_or_create, je sais, et ce ne sera peut-être pas très convivial entre bases de données, mais cela vaut la peine d'être examiné?

9
Mike Woodhouse

Les lettres majuscules et minuscules ne diffèrent que par un seul bit. Le moyen le plus efficace de les rechercher est d’ignorer ce bit, de ne pas convertir les valeurs inférieure ou supérieure, etc. Voir Mots clés COLLATION pour MSSQL, voir NLS_SORT=BINARY_CI si vous utilisez Oracle, etc.

6
Dean Radcliffe

Une autre approche que personne n'a mentionnée consiste à ajouter des outils de recherche insensibles à la casse dans ActiveRecord :: Base. Les détails peuvent être trouvés ici . L'avantage de cette approche est qu'il n'est pas nécessaire de modifier tous les modèles ni d'ajouter la clause lower() à toutes vos requêtes ne tenant pas compte de la casse. Vous utilisez simplement une méthode différente du Finder.

5
Alex Korban

Find_or_create est maintenant obsolète, vous devriez utiliser une relation AR plutôt que first_or_create, comme ceci:

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

Cela renverra le premier objet correspondant ou en créera un pour vous s'il n'en existe aucun.

4
superluminary

Il y a beaucoup de bonnes réponses ici, en particulier @ oma. Mais vous pouvez également essayer une sérialisation personnalisée des colonnes. Si tout ce qui est stocké en minuscule dans votre base de données ne vous gêne pas, vous pouvez créer:

# 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

Puis dans votre modèle:

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

L'avantage de cette approche est que vous pouvez toujours utiliser tous les outils de recherche classiques (y compris find_or_create_by) sans utiliser d'étendues ou de fonctions personnalisées, ni d'avoir lower(name) = ? dans vos requêtes.

L'inconvénient est que vous perdez les informations de casage dans la base de données.

2
Nate Murray

La recherche insensible à la casse est intégrée à Rails. Il rend compte des différences dans les implémentations de base de données. Utilisez soit la bibliothèque intégrée Arel, ou un bijou comme Squeel .

2
Dogweather

Vous pouvez également utiliser des étendues comme celle-ci ci-dessous et les mettre dans une préoccupation et les inclure dans des modèles dont vous pourriez avoir besoin:

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

Puis utilisez comme ceci: Model.ci_find('column', 'value')

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

En supposant que vous utilisiez mysql, vous pourriez utiliser des champs non sensibles à la casse: http://dev.mysql.com/doc/refman/5.0/fr/case-sensitivity.html

0
marcgg

Similaire à Andrews qui est # 1:

Quelque chose qui a fonctionné pour moi est:

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

Ceci élimine le besoin de faire un #where et #first dans la même requête. J'espère que cela t'aides!

0

Une alternative peut être

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

Certaines personnes montrent en utilisant LIKE ou ILIKE, mais celles-ci permettent des recherches de regex. Aussi, vous n'avez pas besoin de minimiser vos achats en Ruby. Vous pouvez laisser la base de données le faire pour vous. Je pense que cela peut être plus rapide. De plus, first_or_create peut être utilisé après 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"> 
0
6ft Dan