Статьи

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

Значок поиска

В этой статье вы узнаете, как интегрировать ElasticSearch в приложение Rails.

Механизм полнотекстового поиска проверяет все слова в каждом сохраненном документе, пытаясь соответствовать критериям поиска (текст, указанный пользователем) википедии . Например, если вы хотите найти статьи, в которых рассказывается о Rails, вы можете искать, используя термин «rails». Если у вас нет специальной методики индексирования, это означает, что нужно полностью сканировать все записи, чтобы найти совпадения, что будет крайне неэффективно. Одним из способов решения этой проблемы является «инвертированный индекс», который отображает слова в содержании всех записей в их местоположение в базе данных.

Например, если индекс первичного ключа такой:

article#1 -> "breakthrough drug for schizophrenia"
article#2 -> "new schizophrenia drug"
article#3 -> "new approach for treatment of schizophrenia"
article#4 -> "new hopes for schizophrenia patients"
...

Инвертированный индекс для этих записей будет выглядеть так:

 breakthrough    -> article#1
drug            -> article#1, article#2
schizophrenia   -> article#1, article#2, article#3, article#4
approach        -> article#3
new             -> article#2, article#3, article#4
hopes           -> article#4
...

Теперь при поиске слова «наркотик» используется инвертированный индекс и возвращаются статья № 1 и статья № 2 напрямую.

Я рекомендую IR Book , если вы хотите узнать больше об этом.

Создайте приложение статей

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

Создать приложение Rails

Введите в командной строке следующее:

 $ rails new blog
$ cd blog
$ bundle install
$ rails s

Создайте контроллер статей

Создайте контроллер статей с помощью генератора Rails, добавьте маршруты в config / rout.rb и добавьте методы для отображения, создания и вывода статей.

 $ rails g controller articles

Затем откройте config / rout.rb и добавьте этот ресурс:

 Blog::Application.routes.draw do
  resources :articles
end

Теперь откройте app / controllers / article_controller.rb и добавьте методы для создания, просмотра и составления списка статей.

 def index
  @articles = Article.all
end

def show
  @article = Article.find params[:id]
end

def new
end

def create
  @article = Article.new article_params
  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

private
  def article_params
    params.require(:article).permit :title, :text
  end

Модель статьи

Нам понадобится модель для статей, поэтому сгенерируйте ее так:

 $ rails g model Article title:string text:text
$ rake db:migrate

Взгляды

Форма новой статьи

Создайте новый файл в app / views / article / new.html.erb со следующим содержимым:

 <h1>New Article</h1>  

<%= form_for :article, url: articles_path do |f| %>

  <% if not @article.nil? and @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>

  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to '<- Back', articles_path %>

Показать одну статью

Создайте другой файл в app / views / article / show.html.erb :

 <p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<%= link_to '<- Back', articles_path %>

Список всех статей

Создайте третий файл в app / views / article / index.html.erb :

 <h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <h3>
        <%= article.title %>
      </h3>
      <p>
        <%= article.text %>
      </p>
    </li>
  <% end -%>
</ul>
<%= link_to 'New Article', new_article_path %>

Теперь вы можете добавлять и просматривать статьи. Обязательно запустите сервер Rails и перейдите по адресу http: // localhost: 3000 / article . Нажмите «Новая статья» и добавьте несколько статей. Они будут использованы для проверки наших возможностей полнотекстового поиска.

Интегрировать ElasticSearch

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

Установить для Ubuntu и Mac

Ubuntu

Перейдите наasticsearch.org/ download и загрузите файл DEB . Как только файл будет локальным, введите:

 $ sudo dpkg -i elasticsearch-[version].deb

макинтош

Если вы работаете на Mac, Homebrew делает это легко:

 $ brew install elasticsearch

Подтвердите установку

Откройте этот URL: http: // localhost: 9200, и вы увидите, что ElasticSearch отвечает так:

 {
  "status" : 200,
  "name" : "Anvil",
  "version" : {
    "number" : "1.2.1",
    "build_hash" : "6c95b759f9e7ef0f8e17f77d850da43ce8a4b364",
    "build_timestamp" : "2014-06-03T15:02:52Z",
    "build_snapshot" : false,
    "lucene_version" : "4.8"
  },
  "tagline" : "You Know, for Search"
}

Добавить базовый поиск

Создайте контроллер с именем search/ search? Q = ruby .

Gemfile

 gem 'elasticsearch-model'
gem 'elasticsearch-rails'

Не забудьте запустить bundle install

Контроллер поиска

Создайте SearchController

 $ rails g controller search

Добавьте этот метод в app / controller / search_controller.rb :

 def search
  if params[:q].nil?
    @articles = []
  else
    @articles = Article.search params[:q]
  end
end

Интегрируйте поиск в статью

Чтобы добавить интеграцию ElasticSearch к модели Article, потребуйте эластичный поиск elasticsearch/modelArticle

Изменить приложение / models / article.rb :

 require 'elasticsearch/model'

class Article < ActiveRecord::Base
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
end
Article.import # for auto sync model with elastic search

Поиск в представлении

Создайте новый файл в app / views / search / search.html.erb :

 <h1>Articles Search</h1>

<%= form_for search_path, method: :get do |f| %>
  <p>
    <%= f.label "Search for" %>
    <%= text_field_tag :q, params[:q] %>
    <%= submit_tag "Go", name: nil %>
  </p>
<% end %>

<ul>
  <% @articles.each do |article| %>
    <li>
      <h3>
        <%= link_to article.title, controller: "articles", action: "show", id: article._id%>
      </h3>
    </li>
  <% end %>
</ul>

Маршрут поиска

Добавьте поисковый маршрут к _config / rout.rb -:

 get 'search', to: 'search#search'

Теперь вы можете перейти на http: // localhost: 3000 / search и найти любое слово в созданных вами статьях.

Улучшить поиск

Вы можете заметить, что в вашей поисковой системе есть некоторые ограничения. Например, поиск части слова, например «rub» или «roby» вместо «ruby» , даст нулевой результат. Кроме того, было бы неплохо, если бы поисковая система давала результаты, содержащие слова, похожие на ваш поисковый запрос.

ElasticSearch предоставляет множество функций для улучшения поиска. Я приведу несколько примеров.

Пользовательский запрос

Существуют разные типы запросов, которые мы можем использовать. Пока что мы просто используем запрос ElasticSearch по умолчанию. Чтобы улучшить результаты поиска, нам нужно изменить этот запрос по умолчанию. Мы можем, например, дать более высокий приоритет для таких полей, как заголовок над другими полями.

ElasticSearch предоставляет полный Query DSL на основе JSON для определения запросов. В общем, есть основные запросы, такие как термин или префикс. Есть также составные запросы, такие как запрос bool. С запросами также могут быть связаны фильтры, например фильтрованные запросы или запросы constant_score.

Давайте добавим собственный метод поиска к нашей модели статьи в app / models / article.rb :

 def self.search(query)
  __elasticsearch__.search(
    {
      query: {
        multi_match: {
          query: query,
          fields: ['title^10', 'text']
        }
      }
    }
  )
end

Примечание. ^ 10 увеличивает на 10 баллов количество совпадений, когда искомое условие соответствует названию.

Custom Mapping

Сопоставление — это процесс определения того, как документ должен быть сопоставлен с поисковой системой, включая его характеристики для поиска, например, какие поля доступны для поиска и если / как они маркированы.

Явное отображение определяется на уровне индекса / типа. По умолчанию нет необходимости определять явное сопоставление, поскольку оно автоматически создается и регистрируется при вводе нового типа или нового поля (без снижения производительности) и имеет разумные значения по умолчанию. Только когда необходимо переопределить значения по умолчанию, должно быть предоставлено определение отображения.

Мы улучшим поиск, чтобы вы могли искать такой термин, как «поиск», и получать результаты, в том числе «поиски» и «поиск» .. и т. Д. Это позволит использовать встроенный анализатор английского языка в ElasticSearch, чтобы применить определение слова перед индексацией.

Добавьте это отображение в класс Article: app / models / article.rb

 settings index: { number_of_shards: 1 } do
  mappings dynamic: 'false' do
    indexes :title, analyzer: 'english'
    indexes :text, analyzer: 'english'
  end
end

Рекомендуется добавить следующие строки в конец файла, чтобы автоматически удалять и перестраивать индекс при загрузке article.rb:

 # Delete the previous articles index in Elasticsearch
Article.__elasticsearch__.client.indices.delete index: Article.index_name rescue nil

# Create the new index with the new mapping
Article.__elasticsearch__.client.indices.create \
  index: Article.index_name,
  body: { settings: Article.settings.to_hash, mappings: Article.mappings.to_hash }

# Index all article records from the DB to Elasticsearch
Article.import

Подсветка поиска

По сути, мы хотели бы показать те части статей, где появляется искомый термин. Это похоже на поиск в Google, и вы видите образец документа, в котором ваш термин выделен жирным шрифтом. В ElasticSearch это называется «основные моменты». Мы добавим параметр подсветки в наш запрос и укажем поля, которые мы хотим выделить. ElasticSearch вернет термин между тег, а также несколько слов до и после термина.

Предполагая, что мы ищем термин «с открытым исходным кодом» , ElasticSearch вернет что-то вроде этого:

 Elasticsearch is a flexible and powerful <em>opensource</em>, distributed, real-time search and analytics

Обратите внимание, что «opensource» окружен тег.

Добавить основные моменты в SearchController

Сначала добавьте параметр «highlight» в запрос ElasticSearch:

 def self.search(query)
  __elasticsearch__.search(
    {
      query: {
        multi_match: {
          query: query,
          fields: ['title^10', 'text']
        }
      },
      highlight: {
        pre_tags: ['<em>'],
        post_tags: ['</em>'],
        fields: {
          title: {},
          text: {}
        }
      }
    }
  )
end

Показать основные моменты в представлении

Это довольно легко показать эту изюминку в представлении. Перейдите в app / views / search / search.html.erb и замените элемент ul

 <ul>
  <% @articles.each do |article| %>
    <li>
      <h3>
        <%= link_to article.try(:highlight).try(:title) ? article.highlight.title[0].html_safe : article.title,
          controller: "articles",
          action: "show",
          id: article._id%>
      </h3>
      <% if article.try(:highlight).try(:text) %>
        <% article.highlight.text.each do |snippet| %>
          <p><%= snippet.html_safe %>...</p>
        <% end %>
      <% end %>
    </li>
  <% end %>
</ul>

Теперь добавьте стиль для в приложении / assets / stylesheets / search.css.scss :

 em {
  background: yellow;
}

И последнее, что нам нужно, это выделенный термин, возвращаемый ElasticSearch, который будет заключен в несколько слов. Если вам нужно показать заголовок с начала, добавьте index_options: 'offsets'

 settings index: { number_of_shards: 1 } do
  mappings dynamic: 'false' do
    indexes :title, analyzer: 'english', index_options: 'offsets'
    indexes :text, analyzer: 'english'
  end
end

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

Ссылки