Полнотекстовый поиск в Rails

Введение

Поиск записей является общим требованием для веб-приложений. Как правило, для большинства из них ставится требование, чтобы пользователи могли быстро получить доступ к данным большой записи, которые им нужны.

Хотя это вполне можно сделать, используя простые запросы SQL, иногда более эффективно использовать поисковый движок.

Solr является популярной поисковой платформой проекта Apache Lucene. Ее основные возможности включают мощный полнотекстовый поиск, подсветку обнаруженных фрагментов совпадений, поиск по полному совпадению, индексацию, близкую к реальному времени, динамическую кластеризацию, интеграцию баз данных, разнообразную обработку документов и геоориентированный поиск.

В этой статье мы рассмотрим выполнение полнотекстового поиска с помощью Sunspot, библиотеки, позволяющей интегрировать Solr в другие приложения.

Что вы будете создавать:

поиск в Rails

Настройка проекта

Я создала простое приложение на Github, которое буду использовать, вместо того, чтобы начинать с нового проекта.

Приложение выводит список товаров с названием, изображением, ценой и описанием продукта.

Я уже добавила некоторые исходные данные, так что вы можете запускать rake db:seed, если не хотите сами вводить данные.

Приложение использует Paperclip для вложенных изображений, и, поскольку я использую изменение размеров изображений, на вашей системе должен быть установлен ImageMagick. На вашем компьютере также должен быть установлен Java Runtime.

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

Настройка проекта

Поиск

Мы начнем с включения джемов Sunspot и Solr в Gemfile. Для разработки мы будем использовать джем sunspot_solr, который содержится в дистрибутиве Solr, поэтому нам не нужно будет устанавливать его отдельно:

gem 'sunspot_rails'
 
group :development do
    gem 'sunspot_solr'
end

Запустите bundle install, а затем запустите следующую команду, чтобы сгенерировать файл конфигурации Sunspot:

rails generate sunspot_rails:install

Эта команда создает файл /config/sunspot.yml, в котором вашему приложению указывается, где найти сервер Solr.

Чтобы настроить объекты, которые вы хотите проиндексировать, добавьте к объектам поисковый блок. В стартовом проекте, у нас есть модель Product с такими полями: название, цена, описание и фото. Мы разрешаем поиск полного текста по значениям полей названия и описания.

В файл /models/product.rb добавляем:

searchable do
    text :name, :description
end

Запустите сервер Solr, выполнив команду:

rake sunspot:solr:start

Sunspot индексирует новые записи, которые вы создаете, но если у вас до этого уже существовали в базе данных некоторые записи, запустите rake sunspot:reindex, чтобы они также были проиндексированы.

Затем мы добавляем код в контроллер продуктов, который принимает вводимые пользователем в строку поиска данные и передает их в поисковый движок. В приведенном ниже коде мы вызываем search в модели Product и передаем в блок.

Затем вызываем в блоке метод fulltext и передаем строку запроса, по которой мы хотим произвести поиск. Есть несколько методов, которые мы можем здесь использовать, чтобы получить нужные нам результаты поиска.

Результаты поиска затем присваиваются объектам @products, которые будут нам выведены:

def index
    @query = Product.search do
        fulltext params[:search]
    end
    @products = @query.results
end

Запустите приложение, в данный момент можете произвести поиск из доступных продуктов.

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

Это делается с помощью метода boost, который передает значение, определяющее приоритет, назначаемый различным полям. Поле с наибольшим значением будет иметь наибольшую значимость при поиске.

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

Это делается через внесение следующих изменений в файл /models/product.rb:

searchable do
    text :name, :boost => 2
    text :description
end

Переиндексируйте записи, выполнив rake sunspot:reindex, и теперь результаты с искомой фразой в названии продукта, будут выводиться выше тех, что содержат ту же фразу в описании. Чтобы проверить это, вы можете добавить больше записей в базу.

Сегментированный просмотр

Сегментированный просмотр является способом навигации по результатам поиска с помощью различных наборов сопутствующих атрибутов.

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

Сначала добавьте цену в метод searchable файла /models/product.rb:

searchable do
    text :name, :boost => 2
    text :description
    double :price
end

Затем вызовите в контроллере facet. Продукты будут разбиты на диапазоны по цене с интервалом в $100.00. Здесь мы предполагаем, что все продукты стоят меньше, чем $ 500:

def index
    @query = Product.search do
        fulltext params[:search]
 
        facet :price, :range => 0..500, :range_interval => 100
        with(:price, Range.new(*params[:price_range].split("..").map(&:to_i))) if params[:price_range].present?
 
    end
    @products = @query.results
end

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

<div class="row">
    <h3>Результаты поиска</h3>
    <ul>
        <% for row in @query.facet(:price).rows %>
            <li>
                <% if params[:price_range].blank? %>
                    <%= link_to row.value, :price_range => row.value, :search => params[:search] %> (<%= row.count %>)
                <% else %>
                    <%= row.value %> (<%= link_to "X", :price_range => nil %>)
                <% end %>
            </li>
        <% end %>
    </ul>
</div>

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

В нашем примере приложения, если вы задаете на поиск слово ‘camera’, вы увидите следующий список:

100.0..200.0 (2)
200.0..300.0 (1)
300.0..400.0 (1)

Каждый элемент списка представляет собой ссылку, и при нажатии на него вы получите список продуктов, которые отвечают условиям поиска, а также попадают в ценовой диапазон, который вы выбрали.

Ссылка передает оригинальный поисковый запрос и выбранный диапазон в индексное действие. Поскольку диапазон передается в виде строки, мы используем Range.new(*params[:price_range].split(«..»).map(&:to_i)), чтобы преобразовать его обратно в диапазон.

Вы можете использовать условные операторы для вывода более корректных ссылок, например, $100 — $199 (2) вместо 100.0..200.0 (2), но здесь мы не будем вдаваться глубже в эту тему.

Дополнительные настройки

Есть еще ряд настроек, которые можно сделать, чтобы сконфигурировать работу Solr. По умолчанию Sunspot выполняет поиск полного текста путем деления строки поиска на токены на основе пробелов и других разделительных символов при помощи смарт-токенайзера StandardTokenizer.

Все токены приводятся к нижнему регистру, и на поиск задается точное соответствие.

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

Например, вы можете задать в параметрах движка некоторые синонимы.

Тогда пользователи, которые вводят текст, не строго соответствующий записям в базе данных, все же смогут найти похожие результаты. Например, в записях может содержаться слово ‘ipod’.

Вы можете предоставить для него синонимы, такие как ‘iPod’, ‘i-pod’ и ‘i pod’, чтобы увеличить шансы пользователя найти нужную запись.

Еще одна полезная функция — это возможность добавить производные, что позволит Solr находить разные слова с тем же корнем. Например, если пользователь ввел ‘run’, он получит результаты со словами ‘run’ и ‘running’. Или, если он искал ‘walk’, в результаты будут включены данные, которые содержат ‘walk’, ‘walking’, ‘walked’ и так далее.

Настройки Solr содержатся в файле в solr/conf/schema.xml. Через внесение изменений в этот файл вы можете настроить конфигурацию сервера.

Это выходит за рамки данной статьи, однако более подробно с этой темой вы можете ознакомиться в статье по расширенной конфигурации полнотекстового поиска и на Solr wiki.

Заключение

Теперь, чтобы остановить сервер Solr, выполните:

rake sunspot:solr:stop

Мы рассмотрели, как использовать джемы Sunspot, чтобы задействовать поисковый движок Solr в приложении Rails. Помимо настроек, которые мы использовали, есть еще много других опций, которые вы можете задействовать для конфигурации поиска.

Не забудьте ознакомиться с файлом описания для получения более подробной информации.

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

Но если вы хотите найти что-то с более гибкой настройкой, то стоит рассмотреть Solr или другие доступные поисковые движки.

Перевод статьи «Full Text Search in Rails» был подготовлен дружной командой проекта Сайтостроение от А до Я.