Поиск является неотъемлемой частью любого веб-сайта, особенно для контент-ориентированного веб-сайта. Хорошая поисковая система — это быстрая система, которая дает точные результаты. Поиск и фильтр в основном одинаковы в зависимости от того, как вы на это смотрите. В этой статье я буду относиться к поиску и фильтрации как к одной и той же функции.
Существует множество существующих методов реализации поиска из инструментов, которые мы используем, например, полнотекстовый поиск 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 .
Спасибо, что прочитали, и я надеюсь, что вы узнали что-то сегодня.