web-dev-qa-db-ger.com

Suche ohne Berücksichtigung der Groß- / Kleinschreibung im Modell Rails

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?

202

Sie müssen hier wahrscheinlich ausführlicher sein

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

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.

99
oma

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

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

21
Sohan

In postgres:

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

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).

9
Brad Werth

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?

9
Mike Woodhouse

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.

6
Dean Radcliffe

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.

5
Alex Korban

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.

4
superluminary

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.

2
Nate Murray

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 .

2
Dogweather

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')

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

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

0
marcgg

Ä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!

0

Eine Alternative kann sein

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

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