Поиск без учета регистра в модели 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 ]

2 голосов
/ 09 марта 2015

Здесь много хороших ответов, особенно @ oma. Но есть еще одна вещь, которую вы можете попробовать - использовать пользовательскую сериализацию столбцов. Если вы не возражаете против хранения всего нижнего регистра в вашей базе данных, вы можете создать:

# 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) = ? в своих запросах.

Недостатком является то, что вы теряете информацию об корпусе в базе данных.

2 голосов
/ 07 декабря 2013

Поиск без учета регистра встроен в Rails. Это объясняет различия в реализации баз данных. Используйте либо встроенную библиотеку Arel, либо самоцвет, такой как Squeel .

1 голос
/ 12 ноября 2016

Вы также можете использовать такие области, как показано ниже, поставить их под сомнение и включить в модели, которые могут вам понадобиться:

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

Тогда используйте так: Model.ci_find('column', 'value')

0 голосов
/ 26 апреля 2019

Альтернативой может быть

c = Product.find_by("LOWER(name)= ?", name.downcase)
0 голосов
/ 09 апреля 2019

Аналогично Эндрюсу, который № 1:

Что-то, что сработало для меня:

name = "Blue Jeans"
Product.find_by("lower(name) = ?", name.downcase)

Это исключает необходимость делать #where и #first в одном запросе. Надеюсь, это поможет!

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

Пока что я сделал решение, используя 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 :)
0 голосов
/ 08 февраля 2010

Предполагая, что вы используете mysql, вы можете использовать поля без учета регистра: http://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html

0 голосов
/ 27 марта 2016

Некоторые люди показывают, что используют 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"> 
0 голосов
/ 10 сентября 2014
user = Product.where(email: /^#{email}$/i).first
...