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?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)
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.
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.
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">
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"])
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ć?
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).
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..
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.
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.
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 .
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.
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')
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
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
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">
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 :)
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