Статьи

Освободите ваш поиск в Rails с тегами

Поиск является неотъемлемой частью любого веб-сайта, особенно для контент-ориентированного веб-сайта. Хорошая поисковая система — это быстрая система, которая дает точные результаты. Поиск и фильтр в основном одинаковы в зависимости от того, как вы на это смотрите. В этой статье я буду относиться к поиску и фильтрации как к одной и той же функции.

Существует множество существующих методов реализации поиска из инструментов, которые мы используем, например, полнотекстовый поиск PostgreSQL , который вы можете использовать в Rails . Однако сегодня мы увидим, как реализовать фильтрацию на основе тегов в приложении Rails с использованием PostgreSQL.

Давайте начнем.

О реализации

Давайте посмотрим, как работает реализация на основе тегов. Предположим, у нас есть таблица с названием companies и она имеет следующую цепочку связей отношений:

 companies -> BELONGS_TO -> cities -> BELONGS_TO -> states -> BELONGS_TO -> countries 

В таблице companies есть столбцы, такие как yearly_revenue , employee_strength , started_year и т. Д.

Скажем, у нас есть страница для компаний, и на этой странице есть фильтры. Теперь мы хотим отфильтровать все компании, которые находятся в определенной стране. Этого можно достичь, присоединившись к таблицам и отфильтровав их по предоставленному идентификатору страны, достаточно просто. Но как насчет сценариев, когда фильтры объединяются и становятся более сложными?

Например, мы хотим следующий фильтр:

 Companies that are in New York and started before 2001 and have revenue of more than $200 million and employ more than 1000 people. 

Это все еще достижимо, но запрос расширяется и, по всей вероятности, немного уродливее. Настоящая проблема заключается в упрощении настроек фильтров для пользователей. Одним из способов является размещение соответствующих элементов (текстовых полей и элементов выбора) для каждого фильтра, который мы разрешаем пользователю выполнять, что становится проблемой, когда мы хотим включить все больше и больше фильтров. Если мы хотим сделать 50 различных видов фильтров для информации, будет не очень приятно использовать один элемент на фильтр. Кроме того, что касается запроса, рассмотрим, есть ли отношения между компаниями, идущими в 10 разных направлениях, каждая из которых имеет свой собственный набор ассоциаций? Я уверен, что вы можете видеть, как быстро это становится уродливым. Вот где решение для тегирования — гораздо лучший ответ.

В реализации на основе тегов мы в основном добавляем столбец tag с типом данных массива к таблице, в которой мы хотим выполнить фильтрацию. В нашем случае это таблица companies . Столбец состоит из массива универсально уникальных идентификаторов (UUID), которые соответствуют идентификаторам тегов, к которым относится запись. У нас будет отдельная таблица, называемая tags состоящая из id тега, name и type . Теперь поиск и фильтрация так же просты, как поиск в таблице tags , поиск UUID, а затем фильтрация компаний на основе этих идентификаторов тегов.

Давайте реализуем это с помощью примера.

Пример приложения Rails

Наше приложение Rails будет иметь пять таблиц

  • компании
  • города
  • состояния
  • страны
  • теги

Начните с создания приложения Rails вместе с необходимыми моделями:

 rails new tag-example -d postgresql 

Чтобы реализация тега работала лучше, мы будем использовать UUID в качестве первичного ключа во всех таблицах, поэтому давайте создадим миграцию, чтобы включить расширение uuid-ossp в PostgreSQL:

 rails g migration enable_uuid_ossp 

После создания файла миграции добавьте в него следующую команду:

 enable_extension "uuid-ossp" 

Запустите миграцию:

 rake db:create && rake db:migrate 

Создавайте наши модели, начиная с компаний:

 rails g scaffold companies name founding_year:integer city_id:uuid 

Как я упоминал выше, нам нужно поддерживать поле id в качестве UUID для таблиц. create_companies файл миграции create_companies и измените строку create_table чтобы указать, что в столбце id должен быть uuid . PostgreSQL позаботится об автоматической генерации UUID. Кроме того, добавьте строку для добавления столбца tags в таблицу companies , так как включение его в скаффолд сделает его отображением для пользователя.

 create_table :companies, id: :uuid do |t| t.string :tags, array: true, default: [] 

Далее, создайте модели городов, штатов и стран, каждая из которых делает идентификатор UUID, как упоминалось выше, путем изменения миграций:

 rails g model cities name state_id:uuid rails g model states name country_id:uuid rails g model countries name rails g model tags name tag_type 

После запуска миграции быстро установите отношения в файлах модели:

 ## company.rb belongs_to :city ## city.rb has_many :companies belongs_to :state ## state.rb has_many :cities belongs_to :country ## country.rb has_many :states 

Пример будет более понятным, если мы сгенерируем некоторые начальные данные. В этом упражнении я использую камень faker для получения информации о компании, городе и штате. Вот файл seed ( /db/seed.rb ), который я использовал, вы можете использовать его для справки;

 (1..10).each do |i| country = Country.create(name: Faker::Address.country) (1..10).each do |j| state = State.create(name: Faker::Address.state, country: country) (1..10).each do |k| city = City.create(name: Faker::Address.city, state: state) end end end City.all.each do |city| (1..10).each do |count| company_name = Faker::Company.name Company.create(name: company_name, founding_year: rand(1950..2015), city: city) p "Saved - #{company_name} - #{count}" end end 

Теперь давайте добавим before_save вызов before_save к модели компаний, который будет генерировать теги для компании при каждом сохранении. Это также позаботится о создании записи в таблице тегов. В models / company.rb добавьте следующий код:

 class Company < ActiveRecord::Base belongs_to :city before_save :update_tags def save_location_tags [city, city.state, city.state.country].map do |loc| (Tag.it({id: loc.id, name: loc.name, tag_type: 'LOCATION'}).id rescue nil) end end def update_tags self.tags = [ save_location_tags, (Tag.it({name: founding_year, tag_type: 'COMPANY_FOUNDING_YEAR'}).id rescue nil) ].flatten.compact end end 

Приведенный выше код фактически генерирует и обновляет теги компании при каждом сохранении. В apps / models / tag.rb добавьте следующее:

 class Tag < ActiveRecord::Base def self.it content Tag.where(content).first_or_create! rescue nil end end 

Вы можете добавить столько меток, сколько хотите в метод update_tag . Мы все настроены с точки зрения обновления данных. Теперь давайте реализуем поиск и фильтрацию по тегам.

Фильтр тегов

У нас будет страница для отображения списка компаний и autocomplete при вводе названий тегов. Кроме того, код будет вызывать фильтр каждый раз, когда пользователь делает выбор, отображая текущие выбранные фильтры по мере продвижения.

В app / controllers / companies_controller.rb добавьте строки ниже к действию index :

 def index if params[:tags] @companies = Company.tagged(params[:tags]) else @companies = Company.all.limit(10) end end 

В app / models / company.rb добавьте помеченную область следующим образом:

 scope :tagged, -> (tags) {where('companies.tags @> ARRAY[?]::varchar[]', [tags].flatten.compact)} 

Этот запрос принимает массив тегов и фильтрует все записи, в которых данный массив присутствует в столбце tags .

Теперь мы собираемся использовать расширение pg_trgm для создания автозаполнения для тегов. Как и в случае с другим расширением, нам понадобится выполнить миграцию и включить ее:

 enable extension "pg_trgm" 

Давайте создадим контроллер тегов и добавим к нему конечную точку автозаполнения, которую мы будем использовать из внешнего интерфейса для получения соответствующих идентификаторов тегов для пользовательских запросов:

 rails g controller tags autocomplete 

Добавьте следующий код в метод autocomplete в контроллере тегов:

 def autocomplete results = Tag.select("*, string <-> #{ActiveRecord::Base.sanitize(params[:q])} as distance").order('distance').limit(5) render json: results end 

Теперь у нас есть полный рабочий набор конечных точек фильтра на основе тегов и конечных точек автозаполнения тегов. Вы можете проверить их, запустив сервер Rails ( rails s ) и попробовав следующие конечные точки:

  • Пример автозаполнения тегов: http: // localhost: 3000 / tags / autocomplete? Q = порт
  • Отфильтрованные компании по примеру тегов: http: // localhost: 3000 / companies? Tags [] = ЛЮБОЙ UUID ИЗ ТАБЛИЦЫ TAGS

Я оставлю вас для реализации автозаполнения во внешнем интерфейсе, так как есть много способов и много хороших руководств о том, как этого добиться. Объедините его с нашим набором фильтров, и вы получите мощную систему фильтрации ваших данных.

Вывод

На этом мы пришли к заключению нашего урока. Теперь у нас есть мощная и эффективная система фильтрации и поиска, доступная для интеграции в любое количество таблиц. Его легко использовать в новом проекте или интегрировать в уже существующий.

Весь код, показанный в примере, доступен в github .

Спасибо, что прочитали, и я надеюсь, что вы узнали что-то сегодня.