Wyszukiwanie niewrażliwe na wielkość liter w modelu Rails

Mój model produktu zawiera kilka elementów

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

Teraz importuję niektóre parametry produktu z innego zbioru danych, ale są niespójności w pisowni nazw. Na przykład, w innym zbiorze danych, {[1] } może być napisane Blue Jeans.

Chciałem Product.find_or_create_by_name("Blue Jeans"), ale to stworzy nowy produkt, prawie identyczny z pierwszym. Jakie są moje opcje, jeśli chcę znaleźć i porównać nazwę z małą literą.

Problemy z wydajnością nie są tu tak naprawdę ważne: są tylko 100-200 produktów i chcę to uruchomić jako migrację, która importuje dane.

Jakieś pomysły?
Author: Jesper Rønn-Jensen, 2010-02-08

17 answers

Prawdopodobnie będziesz musiał być bardziej gadatliwy

name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first 
model ||= Product.create(:name => name)
 327
Author: alex.zherdev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-13 20:41:05

To jest kompletna konfiguracja w Rails, dla mojego własnego odniesienia. Cieszę się, że tobie też to pomoże.

Zapytanie:

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

Walidator:

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

Indeks (odpowiedź z unikalny indeks niewrażliwy na wielkość liter w Rails / ActiveRecord?):

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

Szkoda, że nie ma piękniejszego sposobu na zrobienie pierwszego i ostatniego, ale z drugiej strony Rails i ActiveRecord to open source, nie powinniśmy narzekać - możemy zaimplementować go sami i wysłać pull request.

 92
Author: oma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:36

Możesz użyć następującego:

validates_uniqueness_of :name, :case_sensitive => false

Należy pamiętać, że domyślnie ustawienie to: case_sensitive = > false, więc nie musisz nawet pisać tej opcji, jeśli nie zmieniłeś innych sposobów.

Więcej na: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

 21
Author: Sohan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-29 20:01:25

Jeśli używasz Postegres i Rails 4+, to masz możliwość użycia kolumny typu CITEXT, która pozwoli na niewrażliwe na wielkość liter zapytania bez konieczności wypisywania logiki zapytań.

Migracja:

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

I aby go przetestować należy spodziewać się:

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">
 19
Author: Viet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-18 20:07:19

W postgres:

 user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
 13
Author: tomekfranek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-04 18:44:45

Cytowanie z dokumentacji SQLite:

Każdy inny znak pasuje do siebie lub jego odpowiednik małej / dużej litery (tj. dopasowanie bez rozróżniania wielkości liter)

...czego nie wiedziałem.Ale działa:

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

Więc możesz zrobić coś takiego:

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

NIE #find_or_create, wiem, i może nie jest zbyt przyjazny dla bazy danych, ale warto spojrzeć?

 9
Author: Mike Woodhouse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-08 10:04:01

Kilka komentarzy odnosi się do Arel, bez podania przykładu.

Oto przykład wyszukiwania niewrażliwego na wielkość liter:

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

Zaletą tego typu rozwiązania jest to, że jest ono niezależne od bazy danych - będzie używać poprawnych poleceń SQL dla bieżącego adaptera (matches będzie używać ILIKE dla Postgres, a LIKE dla wszystkiego innego).

 7
Author: Brad Werth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-21 01:14:38

Wielkie i małe litery różnią się tylko jednym bitem - najskuteczniejszym sposobem ich przeszukiwania jest zignorowanie tego bitu, a nie konwersja dolnego lub górnego, itp.. Zobacz zestawianie słów kluczowych dla MS SQL, zobacz nls_sort = BINARY_CI jeśli używasz Oracle, itd..

 6
Author: Dean Radcliffe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-29 18:32:21

Innym podejściem, o którym nikt nie wspomniał, jest dodanie finderów niewrażliwych na wielkość liter do metody ActiveRecord:: Base. Szczegóły można znaleźć Tutaj . Zaletą tego podejścia jest to, że nie musisz modyfikować każdego modelu i nie musisz dodawać klauzuli lower() do wszystkich zapytań bez rozróżniania wielkości liter, po prostu używasz innej metody Findera.

 5
Author: Alex Korban,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-24 22:13:19

Find_or_create jest teraz przestarzały, powinieneś zamiast tego użyć relacji AR plus first_or_create, tak:

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

Zwróci pierwszy dopasowany obiekt lub utworzy go dla Ciebie, jeśli żaden nie istnieje.

 4
Author: superluminary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-15 17:09:28

Wyszukiwanie niewrażliwe na wielkość liter jest wbudowane w Rails. Uwzględnia różnice w implementacjach baz danych. Użyj wbudowanej biblioteki Arel, lub klejnotu takiego jak Squeel .

 2
Author: Dogweather,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-06 20:53:26

Jest tu wiele świetnych odpowiedzi, szczególnie @oma. ale jeszcze jedna rzecz, którą możesz spróbować, to użycie niestandardowej serializacji kolumn. Jeśli nie przeszkadza ci, że wszystko jest zapisywane małymi literami w db, możesz utworzyć:

# 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

Następnie w modelu:

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

Zaletą tego podejścia jest to, że nadal możesz używać wszystkich zwykłych finderów (w tym find_or_create_by) bez używania niestandardowych zakresów, funkcji lub posiadania lower(name) = ? w zapytaniach.

Minusem jest to, że tracisz osłonę informacje w bazie danych.

 2
Author: Nate Murray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-09 17:14:43

Możesz również użyć zakresów takich jak ten poniżej i umieścić je w trosce i uwzględnić w modelach, które mogą być potrzebne:

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

Następnie użyj w ten sposób: Model.ci_find('column', 'value')

 1
Author: Damian Simon Peter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-12 00:39:39

Zakładając, że używasz mysql, możesz użyć pól, które nie uwzględniają wielkości liter: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

 0
Author: marcgg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-08 09:37:55
user = Product.where(email: /^#{email}$/i).first
 0
Author: shilovk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-02 16:56:12

Niektórzy ludzie pokazują używając LIKE lub ILIKE, ale te pozwalają na wyszukiwanie regex. Nie musisz też opuszczać Ruby. Możesz pozwolić, aby baza danych zrobiła to za Ciebie. Myślę, że może być szybciej. Również first_or_create może być użyty po 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
Author: 6ft Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-27 01:52:38

Do tej pory stworzyłem rozwiązanie używając Ruby. Umieść to wewnątrz modelu produktu:

  #return first of matching products (id only to minimize memory consumption)
  def self.custom_find_by_name(product_name)
    @@product_names ||= Product.all(:select=>'id, name')
    @@product_names.select{|p| p.name.downcase == product_name.downcase}.first
  end

  #remember a way to flush finder cache in case you run this from console
  def self.flush_custom_finder_cache!
    @@product_names = nil
  end

To da mi pierwszy produkt, w którym nazwy pasują. Albo Zero.

>> Product.create(:name => "Blue jeans")
=> #<Product id: 303, name: "Blue jeans">

>> Product.custom_find_by_name("Blue Jeans")
=> nil

>> Product.flush_custom_finder_cache!
=> nil

>> Product.custom_find_by_name("Blue Jeans")
=> #<Product id: 303, name: "Blue jeans">
>>
>> #SUCCESS! I found you :)
 -7
Author: Jesper Rønn-Jensen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-08 09:38:36