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?
Vous devrez probablement être plus bavard ici
name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first
model ||= Product.create(:name => name)
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.
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">
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
En postgres:
user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
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).
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é?
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.
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.
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.
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.
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 .
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')
user = Product.where(email: /^#{email}$/i).first
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
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!
Une alternative peut être
c = Product.find_by("LOWER(name)= ?", name.downcase)
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">