railsから全文検索エンジンelasticsearchを利用する

ElasticSearchとは

ElasticSearchはSolrと同じApache Lucene上で稼働するオープンソースの全文検索システムです。
ElasticSearchの特徴としては、REST APIが整備されていて、JSONですべてやり取りできるところです。

準備

ElasticSearchはJavaで動作するので事前にJavaのインストールが必要です。
ElasticSearchのインストールはMacならbrewがあるので簡単です。

$ brew install elasticsearch

WindowsでもZipをダウンロードして、batファイル叩くだけだったので楽です。

ElasticSearchには様々なプラグインが用意されており、その中に日本語対応の物も用意されています。
kuromojiを用いたelasticsearch-analysis-kuromojiを使います。

プラグインのインストールも簡単で用意されているpluginコマンドにgitの短縮アドレスを渡して叩くだけです。
(バージョン指定は適宜最新のものを参照してください)

$ plugin --install elasticsearch/elasticsearch-analysis-kuromoji/1.5.0

これでkuromojiをプラグインとして利用可能となりました。
日本語対応させる際に、後述するrailsのmodelでフィールド毎にAnalyzerとしてkuromojiを指定することも出来ますし、デフォルトのAnalyzerとして利用したければ設定ファイル(macなら/usr/local/opt/elasticsearch/config/elasticsearch.yml)に以下を追記して指定することも可能です。

index.analysis.analyzer.default.type: custom
index.analysis.analyzer.default.tokenizer: kuromoji_tokenizer

ブラウザからElasticSearchの状態を確認できるプラグインも複数用意されています。
今回は基本的なelasticsearch-headを導入してみます。

$ plugin --install mobz/elasticsearch-head

elasticsearch-headプラグイン導入後はhttp://localhost:9200/_plugin/head/にアクセスすることでブラウザから様々な確認が出来るようになります。

サンプルRailsアプリ

ElasticSearchを扱うためのサンプルアプリを作成します。
bundle initしてGemfilerails追加して'bundle exec rails newします。

全文検索を試したいので、ニュースの記事を扱うようにしてみます。
記事のモデル名をarticleにして、タイトルと本文を持つようにしてみました。

$ bundle exec rake db:create                                                                                                             
$ bundle exec rails g scaffold article title:string body:text                                                                            
$ bundle exec rake db:migrate

連携

RailsとElasticSearchとの連携にはtireを使います。
Gemfilegem 'tire'を追加してbundle intallします。

次にmodelにElasticSearchとの連携を記述します。

app/models/article.rb

class Article < ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks

  mapping do
    indexes :title, analyzer: :kuromoji
    indexes :body, analyzer: :kuromoji
  end

  def self.search(params)
    tire.search(load: true) do
      query {
        string "body:#{params[:search]} title:#{params[:search]}"
      } if params[:search].present?
    end
  end
end

ControllerはArticle.allの呼び出しをElasticSearchからの検索に変更します。

app/controllers/articles_controller.rb#index

def index
    @articles = Article.search(params)
    # @articles = Article.all
end

Viewに検索のボックスを追加します。

app/views/articles/index.html.erb

<%= form_tag articles_path, :method => :get do %>
  <%= text_field_tag :search, params[:search] %>
  <%= submit_tag "Search", :name => nil %>
<% end %>
<%= link_to 'clear', articles_path %>

これで
http://localhost:3000/articlesにアクセスして適当にニュース記事のデータを突っ込んで、検索窓にワードを打ち込めば検索可能になっています。