Ответ 1
Вы, вероятно, должны быть более подробными здесь
name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first
model ||= Product.create(:name => name)
Моя модель продукта содержит некоторые элементы
Product.first
=> #<Product id: 10, name: "Blue jeans" >
Теперь я импортирую некоторые параметры продукта из другого набора данных, но есть несоответствия в написании имен. Например, в другом наборе данных Blue jeans
может быть записано Blue jeans
.
Я хотел Product.find_or_create_by_name("Blue Jeans")
, но это создаст новый продукт, почти идентичный первому. Каковы мои варианты, если я хочу найти и сравнить имя в нижней части.
Проблемы с производительностью здесь не очень важны: есть только 100-200 продуктов, и я хочу запустить это как миграцию, которая импортирует данные.
Любые идеи?
Вы, вероятно, должны быть более подробными здесь
name = "Blue Jeans"
model = Product.where('lower(name) = ?', name.downcase).first
model ||= Product.create(:name => name)
Это полная настройка в 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 - это с открытым исходным кодом, мы не должны жаловаться - мы можем реализовать его сами и отправить запрос на pull.
Если вы используете 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">
Возможно, вы захотите использовать следующее:
validates_uniqueness_of :name, :case_sensitive => false
Обратите внимание, что по умолчанию параметр: case_sensitive = > false, поэтому вам даже не нужно писать этот параметр, если вы не изменили другие способы.
Найти больше по адресу: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#method-i-validates_uniqueness_of
В postgres:
user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
Цитата из документации 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
, я знаю, и это может быть не очень дружественным к базе данных, но стоит посмотреть?
Несколько комментариев относятся к Arel, не приводя пример.
Вот пример Arel для поиска без учета регистра:
Product.where(Product.arel_table[:name].matches('Blue Jeans'))
Преимущество такого типа решений заключается в том, что он не зависит от базы данных - он будет использовать правильные команды SQL для вашего текущего адаптера (matches
будет использовать ILIKE
для Postgres и LIKE
для всего остального).
Прописные и строчные буквы отличаются только на один бит. Наиболее эффективный способ их поиска - игнорировать этот бит, не преобразовывать нижний или верхний и т.д. См. Ключевые слова COLLATION
для MSSQL, см. NLS_SORT=BINARY_CI
при использовании Oracle и т.д.
Другой подход, о котором никто не упомянул, заключается в добавлении нечувствительных к регистру искателей в ActiveRecord :: Base. Подробности можно найти здесь. Преимущество этого подхода состоит в том, что вам не нужно изменять каждую модель, и вам не нужно добавлять предложение lower()
для всех ваших запросов без учета регистра, вы просто используете другой метод поиска вместо этого.
Find_or_create теперь устарел, вы должны использовать AR-отношение вместо first_or_create, например:
TombolaEntry.where("lower(name) = ?", self.name.downcase).first_or_create(name: self.name)
Это вернет первый сопоставленный объект или создаст его для вас, если он не существует.
Нечувствительный к регистру поиск встроен в Rails. Это объясняет различия в реализации баз данных. Используйте встроенную библиотеку Arel или драгоценный камень, такой как Squeel.
Здесь есть много замечательных ответов, особенно @oma's. Но еще одна вещь, которую вы могли бы попробовать, - использовать пользовательскую сериализацию столбцов. Если вы не против того, чтобы все было записано в нижнем регистре в вашем db, вы можете создать:
# 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
Затем в вашей модели:
# app/models/my_model.rb
serialize :name, Serializers::DowncasingStringSerializer
validates_uniqueness_of :name, :case_sensitive => false
Преимущество этого подхода состоит в том, что вы можете использовать все обычные поисковые устройства (включая find_or_create_by
) без использования пользовательских областей, функций или наличия lower(name) = ?
в ваших запросах.
Недостатком является то, что вы теряете информацию об обсадке в базе данных.
Вы также можете использовать области, подобные приведенным ниже, и включить их в проблему и включить их в модели, которые могут вам понадобиться:
scope :ci_find, lambda { |column, value| where("lower(#{column}) = ?", value.downcase).first }
Затем используйте так:
Model.ci_find('column', 'value')
Предполагая, что вы используете mysql, вы можете использовать поля, которые не чувствительны к регистру: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html
user = Product.where(email: /^#{email}$/i).first
Некоторые люди показывают использование LIKE или ILIKE, но они позволяют выполнять поиск в регулярном выражении. Кроме того, в Ruby вам не нужно делать ничего. Вы можете позволить базе данных сделать это за вас. Я думаю, что это может быть быстрее. Также first_or_create
можно использовать после 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">
Подобно Эндрюсу, который является # 1:
Что-то, что сработало для меня:
name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)
Это устраняет необходимость выполнения #where
и #first
в одном запросе. Надеюсь это поможет!
Альтернативой может быть
c = Product.find_by("LOWER(name)= ?", name.downcase)
До сих пор я решил использовать Ruby. Поместите это внутри модели продукта:
#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
Это даст мне первый продукт, где имена совпадают. Или ноль.
>> 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 :)