Mein Produktmodell enthält einige Artikel
Product.first
=> #<Product id: 10, name: "Blue jeans" >
Ich importiere jetzt einige Produktparameter aus einem anderen Datensatz, aber die Schreibweise der Namen ist inkonsistent. Zum Beispiel könnte im anderen Datensatz Blue jeans
Geschrieben werden Blue Jeans
.
Ich wollte Product.find_or_create_by_name("Blue Jeans")
, aber dadurch entsteht ein neues Produkt, das fast identisch mit dem ersten ist. Welche Optionen stehen mir zur Verfügung, wenn ich den Namen in Kleinbuchstaben suchen und vergleichen möchte?.
Leistungsprobleme sind hier nicht wirklich wichtig: Es gibt nur 100-200 Produkte, und ich möchte dies als Migration ausführen, bei der die Daten importiert werden.
Irgendwelche Ideen?
Sie müssen hier wahrscheinlich ausführlicher sein
name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first
model ||= Product.create(:name => name)
Dies ist ein vollständiges Setup in Rails, als meine eigene Referenz. Ich freue mich, wenn es dir auch hilft.
die Abfrage:
Product.where("lower(name) = ?", name.downcase).first
der validator:
validates :name, presence: true, uniqueness: {case_sensitive: false}
den Index (Antwort von Groß-/Kleinschreibung wird in Rails/ActiveRecord nicht berücksichtigt? ):
execute "CREATE UNIQUE INDEX index_products_on_lower_name ON products USING btree (lower(name));"
Ich wünschte, es gäbe eine schönere Möglichkeit, das Erste und das Letzte zu tun, aber andererseits, Rails und ActiveRecord ist Open Source, wir sollten uns nicht beschweren - wir können es selbst implementieren und Pull senden anfordern.
Wenn Sie Postegres und Rails 4+ verwenden, haben Sie die Möglichkeit, den Spaltentyp CITEXT zu verwenden, wodurch Abfragen ohne Berücksichtigung der Groß- und Kleinschreibung zulässig sind, ohne die Abfragelogik ausschreiben zu müssen.
Die 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
Und um es auszuprobieren, sollten Sie folgendes erwarten:
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">
Möglicherweise möchten Sie Folgendes verwenden:
validates_uniqueness_of :name, :case_sensitive => false
Beachten Sie, dass die Einstellung standardmäßig wie folgt lautet: case_sensitive => false, sodass Sie diese Option nicht einmal schreiben müssen, wenn Sie keine anderen Einstellungen vorgenommen haben.
Weitere Informationen finden Sie unter: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of
In postgres:
user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
Einige Kommentare beziehen sich auf Arel, ohne ein Beispiel anzugeben.
Hier ist ein Arel-Beispiel für eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung:
Product.where(Product.arel_table[:name].matches('Blue Jeans'))
Der Vorteil dieser Art von Lösung ist, dass sie datenbankunabhängig ist - sie verwendet die richtigen SQL-Befehle für Ihren aktuellen Adapter (matches
verwendet ILIKE
für Postgres und LIKE
für alles andere).
Zitat aus der SQLite-Dokumentation :
Alle anderen Zeichen stimmen mit sich selbst oder den entsprechenden Klein-/Großbuchstaben überein (d. H. Übereinstimmung ohne Berücksichtigung der Groß-/Kleinschreibung).
... was ich nicht wusste. Aber es funktioniert:
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
Sie könnten also so etwas tun:
name = 'Blue jeans'
if prod = Product.find(:conditions => ['name LIKE ?', name])
# update product or whatever
else
prod = Product.create(:name => name)
end
Nicht #find_or_create
, Ich weiß, und es ist vielleicht nicht sehr datenbankübergreifend, aber einen Blick wert?
Groß- und Kleinbuchstaben unterscheiden sich nur durch ein einziges Bit. Der effizienteste Weg, sie zu durchsuchen, besteht darin, dieses Bit zu ignorieren und weder das untere noch das obere zu konvertieren. Siehe Schlüsselwörter COLLATION
für MSSQL, siehe NLS_SORT=BINARY_CI
bei Verwendung von Oracle usw.
Ein anderer Ansatz, den niemand erwähnt hat, ist das Hinzufügen von Suchern, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird, zu ActiveRecord :: Base. Details finden Sie hier . Der Vorteil dieses Ansatzes besteht darin, dass Sie nicht jedes Modell ändern müssen und nicht die lower()
-Klausel zu allen Abfragen hinzufügen müssen, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird. Stattdessen verwenden Sie einfach eine andere Finder-Methode.
Find_or_create ist jetzt veraltet. Verwenden Sie stattdessen eine AR-Relation und first_or_create, wie folgt:
TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)
Dadurch wird das erste übereinstimmende Objekt zurückgegeben oder ein Objekt für Sie erstellt, falls keines vorhanden ist.
Hier gibt es viele gute Antworten, besonders bei @oma's. Sie können aber auch versuchen, eine benutzerdefinierte Spaltenserialisierung zu verwenden. Wenn es Ihnen nichts ausmacht, wenn in Ihrer Datenbank alles in Kleinbuchstaben gespeichert ist, können Sie Folgendes erstellen:
# 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
Dann in Ihrem Modell:
# app/models/my_model.rb
serialize :name, Serializers::DowncasingStringSerializer
validates_uniqueness_of :name, :case_sensitive => false
Der Vorteil dieses Ansatzes ist, dass Sie weiterhin alle regulären Finder (einschließlich find_or_create_by
) Verwenden können, ohne benutzerdefinierte Bereiche, Funktionen oder lower(name) = ?
in Ihren Abfragen zu verwenden.
Der Nachteil ist, dass Sie Gehäuseinformationen in der Datenbank verlieren.
Die Suche nach Groß- und Kleinschreibung ist in Rails integriert. Es berücksichtigt Unterschiede bei Datenbankimplementierungen. Verwenden Sie entweder die eingebaute Arel-Bibliothek oder einen Edelstein wie Squeel .
Sie können auch Bereiche wie den folgenden verwenden und sie in ein Problem einbeziehen und in Modelle einbeziehen, die Sie möglicherweise benötigen:
scope :ci_find, lambda { |column, value| where("lower(#{column}) = ?", value.downcase).first }
Dann benutze wie folgt: Model.ci_find('column', 'value')
user = Product.where(email: /^#{email}$/i).first
Angenommen, Sie verwenden MySQL, können Sie Felder verwenden, bei denen die Groß- und Kleinschreibung nicht beachtet wird: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
Ähnlich wie Andrews, der # 1 ist:
Etwas, das für mich funktioniert hat, ist:
name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)
Dies beseitigt die Notwendigkeit, ein #where
und #first
in der gleichen Abfrage. Hoffe das hilft!
Eine Alternative kann sein
c = Product.find_by("LOWER(name)= ?", name.downcase)
Einige Leute zeigen mit LIKE oder ILIKE, aber diese erlauben Regex-Suchen. Außerdem müssen Sie Ruby nicht in den Downcase-Modus versetzen. Sie können die Datenbank dies für Sie tun lassen. Ich denke, es kann schneller sein. Ebenfalls first_or_create
kann nach where
verwendet werden.
# 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">