Поиск без учета регистра в модели Rails - PullRequest
201 голосов
/ 08 февраля 2010

Моя модель продукта содержит некоторые элементы

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

Сейчас я импортирую некоторые параметры продукта из другого набора данных, но в написании имен есть несоответствия. Например, в другом наборе данных Blue jeans может быть записано Blue Jeans.

Я хотел Product.find_or_create_by_name("Blue Jeans"), но это создаст новый продукт, почти идентичный первому. Какие есть варианты, если я хочу найти и сравнить имя в нижнем регистре.

Проблемы с производительностью здесь не очень важны: есть только 100-200 продуктов, и я хочу запустить их как миграцию, которая импортирует данные.

Есть идеи?

Ответы [ 19 ]

343 голосов
/ 08 февраля 2010

Вам, вероятно, придется быть более многословным здесь

name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first 
model ||= Product.create(:name => name)
97 голосов
/ 06 марта 2013

Это полная настройка в Rails, для моей справки. Я рад, если вам это тоже поможет.

запрос:

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

Валидатор:

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

индекс (ответ от Уникальный регистр без учета регистра в Rails / ActiveRecord? ):

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

Хотелось бы, чтобы был более красивый способ сделать первое и последнее, но опять же, Rails и ActiveRecord - это открытый исходный код, нам не следует жаловаться - мы можем реализовать это сами и отправить запрос на извлечение.

22 голосов
/ 18 июля 2016

Если вы используете Postegres и Rails 4+, то у вас есть возможность использовать тип столбца CITEXT, что позволит выполнять запросы без учета регистра без необходимости записывать логику запроса.

Миграция:

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

И чтобы проверить это, вы должны ожидать следующее:

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">
21 голосов
/ 08 февраля 2010

Возможно, вы захотите использовать следующее:

validates_uniqueness_of :name, :case_sensitive => false

Обратите внимание, что по умолчанию используется значение case_sensitive => false, поэтому вам даже не нужно писать эту опцию, если вы не изменили другие способы.

Найти больше на: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of

13 голосов
/ 06 марта 2012

В постгрес:

 user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
9 голосов
/ 08 февраля 2010

Цитирование из документации SQLite :

Любой другой символ соответствует самому себе или его эквивалент в нижнем / верхнем регистре (т.е. сопоставление без учета регистра)

... чего я не знал. Но это работает:

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

Так что вы можете сделать что-то вроде этого:

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

Не #find_or_create, я знаю, и это может быть не очень дружелюбно к базе данных, но стоит посмотреть?

8 голосов
/ 21 июня 2017

Несколько комментариев относятся к Арелу, без примера.

Вот пример Arel для поиска без учета регистра:

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

Преимущество этого типа решения в том, что оно не зависит от базы данных - оно будет использовать правильные команды SQL для вашего текущего адаптера (matches будет использовать ILIKE для Postgres и LIKE для всего остального).

6 голосов
/ 29 июля 2010

Прописные и строчные буквы отличаются только одним битом. Наиболее эффективный способ их поиска - игнорировать этот бит, не преобразовывать нижний или верхний и т. Д. См. Ключевые слова COLLATION для MSSQL, см. NLS_SORT=BINARY_CI при использовании Oracle и т. Д.

5 голосов
/ 03 апреля 2010

Другой подход, о котором никто не упомянул, заключается в добавлении нечувствительных к регистру искателей в ActiveRecord :: Base. Подробности можно найти здесь . Преимущество этого подхода заключается в том, что вам не нужно изменять каждую модель, и вам не нужно добавлять предложение lower() ко всем запросам без учета регистра, вы просто используете другой метод поиска вместо этого.

4 голосов
/ 02 июля 2013

Find_or_create теперь устарела, вместо этого вы должны использовать AR Relation плюс first_or_create, вот так:

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

Это вернет первый совпавший объект или создаст его для вас, если ни один не существует.

...