読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

RailsでElasticsearch: ハイライト(Highlight)

elasticsearch 検索

f:id:nipe880324:20151017170217p:plain:w420

RailsでElasticsearchを使って検索機能を実装してきました。
今回は、「Elasticsearchのハイライト機能」について簡単に説明します。

参考までに、ソースコードはこちらです。elasticsearch_test - GitHub

動作確認

  • Mac OS X 10.11 El Capitan
  • elasticsearch 1.7.2
  • Rails 4.2.3
  • elasticsearch-dsl 0.1.2
  • elasticsearch-model 0.1.8
  • elasticsearch-rails 0.1.8

1. ハイライト機能を実装

次のようにハイライト機能を実装します。
f:id:nipe880324:20151025010952p:plain:w420


Elasticsearchのハイライトクエリを作成

Highlighting - Elasticsearch Documentationによると、"highlight"キーを指定することで、ハイライトを行えます。

highlight.rb - elasticsearch-dslを参考にし、searchメソッドにハイライトキーを追加します。

# app/models/restaurant.rb
class Restaurant < ActiveRecord::Base
  ...

  def self.search(params = {})
    ...
    search_definition = Elasticsearch::DSL::Search.search {
      query { ... }

      # highlightキーを指定
      #   pre_tagsでマッチした文字列の前(pre)に追加するタグを指定
      #   post_tagsでマッチした文字列の後(post)に追加するタグを指定
      #   filedsでハイライトを行うフィールドを指定
      #
      #   マッチした場合次のようになります。
      #   <highlight>文字列</highlight>
      highlight {
        pre_tags ['<highlight>']
        post_tags ['</highlight>']
        fields %w{ name name_kana address pref.name category.name }
      }
    }
    ...
  end
end

レスポンスの確認

クエリでhighlightを指定したので、レスポンスを確認します。

# 「牛角」で検索したときのレスポンス
response = __elasticsearch__.search(search_definition)
result = response.first
=> #<Elasticsearch::Model::Response::Result:0x007fa5f5509400
 @result=
  {"_index"=>"restaurant_development",
   "_type"=>"restaurant",
   "_id"=>"605",
   "_score"=>nil,
   "_source"=>
    {"name"=>"牛角",
     "name_kana"=>"ぎゅうかく",
     "zip"=>"130-0033",
     "address"=>"池袋3-33",
     "closed"=>false,
     "created_at"=>"2015-10-18T11:41:12.335Z",
     "pref"=>{"name"=>"東京都"},
     "category"=>{"name"=>"居酒屋"}},
   "highlight"=>{"name"=>["<highlight>牛</highlight><highlight>角</highlight>"]},
   "sort"=>[1445168472335]}>

# highlightキーに格納されています
result.highlight
# => {"name"=>["<highlight>牛</highlight><highlight>角</highlight>"]}

# マッチしたフィールド名で取得できます
# 形態素解析のkuromojiが「牛」と「角」に分割しているので次のようになっている。
# そこら辺は辞書登録などアナライザーをチューニングする必要があるがここでは省略します
result.highlight.name
# => ["<highlight>牛</highlight><highlight>角</highlight>"]


ハイライト文字列を表示するヘルパーを作成

ハイライトの値を表示するヘルパーメソッドを作成します。

# app/helpers/application_helper.rb
module ApplicationHelper
  ...

  # ハイライト文字列(<highlight>[文字列]</highlight>)か文字列([文字列])を返す
  #   result [Elasticsearch::Model::Response::Result] を指定
  #   field  フィールド名
  def highlight_or_text(result, field)
    highlight_for_result(result, field) || text_for_result(result, field)
  end

  # ハイライト文字列を返す
  # 存在しな場合もあるのでtryを行っている
  def highlight_for_result(result, field)
    result.try(:highlight).try(field.to_sym).try(:join).try(:html_safe)
    end

  # 文字列を返す
  # pref.nameなどに対応できるようにするためにinjectメソッドを利用しています
  def text_for_result(result, field)
    field.to_s.split('.').inject(result) { |result, field| result.send(field.to_sym) }
  end
end

※これらのメソッドはresultを使っているので、[Elasticsearch::Model::Response::Result]をラッピングさせたResultクラスを作って、その中で処理をさせたほうがよいと思います。


ビューでハイライトさせる

ハイライト文字列はhighlightタグで囲むようにしていますので、スタイリングを追加します。

/* app/assets/stylesheets/application.css */

...
highlight {
  color: #d9534f;
  font-style: italic;
}

そして、ビューファイルで先程のhighlight_or_textヘルパーメソッドを呼び出します。

<!-- app/views/top/index.html.erb -->

...

<!-- 検索結果の表示 -->
<div class="col-xs-9">
...

  <div id="results">
    <% @restaurants.each do |r| %>
      <hr />
      <div class="result">
        <!-- highlight_or_textヘルパーメソッドに変更する -->
        <h4><%= highlight_or_text(r, :name) %><%= highlight_or_text(r, :name_kana) %></h4>
        <p class="text-muted">
          <small><b>都道府県:</b> <%= highlight_or_text(r, :'pref.name') %></small>
          <small><b>カテゴリ:</b> <%= highlight_or_text(r, :'category.name') %></small>
          <small><b>出店日:</b> <%= r.created_at %></small>
        </p>
      </div>
    <% end %>
  </div>
</div>


ハイライトの画面確認

次のように「牛角」で検索すると牛角が赤文字で表示されます。
f:id:nipe880324:20151025010952p:plain:w420

まとめ

Elasticsearchでhighlihgtキーを指定することで、ハイライト機能を実装しました。レスポンスでは、マッチした文字列がある場合、highlightに値が入ります。
次は、「Elasticsearchのサジェスト機能を使用してオートコンプリート機能」を実装します。

参考文献