Поиск без учета регистра в модели Rails

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

 Product.first
 => #

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

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

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

Есть идеи?

вопрос задан 8.02.2010
Jesper Rønn-Jensen
40389 репутация

17 ответов


  • 327 рейтинг

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

    name = "Blue Jeans"
    model = Product.where('lower(name) = ?', name.downcase).first 
    model ||= Product.create(:name => name)
    
    ответ дан alex.zherdev, с репутацией 20146, 8.02.2010
  • 92 рейтинг

    Это полная настройка в 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 с открытым исходным кодом, мы не должны жаловаться - мы можем реализовать это сами и отправить запрос на извлечение.

    ответ дан oma, с репутацией 12779, 6.03.2013
  • 21 рейтинг

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

    validates_uniqueness_of :name, :case_sensitive => false
    

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

    Узнайте больше на: http: // api. Рубин на рельсах. орг / классы / ActiveRecord / валидация /. методы класса html # method-i-validates_uniqueness_of

    ответ дан Sohan, с репутацией 3092, 8.02.2010
  • 19 рейтинг

    Если вы используете 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.find_by(name: 'joggers')
    => #
    
    Product.find_by(name: 'JOGGERS')
    => #
    
    ответ дан Viet, с репутацией 2164, 18.07.2016
  • 13 рейтинг

    В postgres:

     user = User.find(:first, :conditions => ['username ~* ?', "regedarek"])
    
    ответ дан tomekfranek, с репутацией 3083, 5.03.2012
  • 9 рейтинг

    Цитирование из документации 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, я знаю, и это может быть не очень дружелюбно к базе данных, но стоит посмотреть?

    ответ дан Mike Woodhouse, с репутацией 42647, 8.02.2010
  • 7 рейтинг

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

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

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

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

    ответ дан Brad Werth, с репутацией 13769, 20.06.2017
  • 6 рейтинг

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

    ответ дан Dean Radcliffe, с репутацией 1646, 29.07.2010
  • 5 рейтинг

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

    ответ дан Alex Korban, с репутацией 12020, 3.04.2010
  • 4 рейтинг

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

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

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

    ответ дан superluminary, с репутацией 28285, 2.07.2013
  • 2 рейтинг

    Здесь есть много отличных ответов, особенно @ 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) = ?.

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

    ответ дан Nate Murray, с репутацией 1795, 9.03.2015
  • 2 рейтинг

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

    ответ дан Dogweather, с репутацией 6648, 6.12.2013
  • 1 рейтинг

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

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

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

    ответ дан Damian Simon Peter, с репутацией 779, 12.11.2016
  • 0 рейтинг
    user = Product.where(email: /^#{email}$/i).first
    
    ответ дан shilovk, с репутацией 4789, 10.09.2014
  • 0 рейтинг

    Предполагая, что вы используете mysql, вы можете использовать поля без учета регистра: http: // dev. MySQL. ком / док / RefMan / 5. 0 / о / чувствительность к регистру. HTML

    ответ дан marcgg, с репутацией 41146, 8.02.2010
  • 0 рейтинг

    Некоторые люди показывают, что используют 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
    # => # 
    
    ответ дан 6ft Dan, с репутацией 2572, 27.03.2016
  • -7 рейтинг

    До сих пор я сделал решение, используя 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.custom_find_by_name("Blue Jeans")
    => nil
    
    >> Product.flush_custom_finder_cache!
    => nil
    
    >> Product.custom_find_by_name("Blue Jeans")
    => #
    >>
    >> #SUCCESS! I found you :)
    
    ответ дан Jesper Rønn-Jensen, с репутацией 40389, 8.02.2010