酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

Elasticsearchを使ったRailsサンプルアプリの作成

検索機能を実装するときによく使われているElasticsearchをRailsで使うためのサンプルアプリの作成の手順を作りました。入門レベルです!


Elasticsearchの導入

拙著『Elasticsearch 2.1 + Kibana 4.1 + Marvel のMacへのセットアップ』を良ければご参考ください。

Rails サンプルアプリ

Railsのサンプルアプリを作成します。今回は Article(記事) のモデルを持つことにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Railsアプリの作成
rails new elasticsearch_sample --skip-bundle

# フォルダを移動
cd elasticsearch_sample

# DBの作成
rake db:create

# article テーブルの定義を作成
bundle exec rails g scaffold article title:string body:text

# article テーブルを作成
bundle exec rake db:migrate

GemfileにElasticsearch用のgemを追加します。

1
2
3
4
5
# Gemfile

# Elasticsearch
gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'

追加したらターミナルで以下のコマンドを実行してgemをインストールします。

1
bundle install --jobs=4 --path=vendor/bundle

ModelにElasticsearchを使うための設定

次にArticleモデルにElasticsearchを使うための設定をします。

1
2
3
4
# app/models/article.rb
class Article < ActiveRecord::Base
  include ArticleSearchable
end

今回はconcernにElasticsearchに関する処理を切り出します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# app/models/concerns/article_searcable.rb
module ArticleSearchable
  extend ActiveSupport::Concern

  included do
    include Elasticsearch::Model

    # インデックスするフィールドの一覧
    INDEX_FIELDS = %w(title body).freeze

    # インデックス名
    index_name "es_sample_article_#{Rails.env}"

    # マッピング情報
    settings do
      mappings dynamic: 'false' do # 動的にマッピングを生成しない
        indexes :title, analyzer: 'kuromoji', type: 'string'
        indexes :body,  analyzer: 'kuromoji', type: 'string'
      end
    end

    # インデックスするデータを生成
    # @return [Hash]
    def as_indexed_json(option = {})
      self.as_json.select { |k, _| INDEX_FIELDS.include?(k) }
    end
  end

  module ClassMethods
    # indexの作成メソッド
    def create_index!
      client = __elasticsearch__.client
      client.indices.delete index: self.index_name rescue nil
      client.indices.create(index: self.index_name,
                            body: {
                                settings: self.settings.to_hash,
                                mappings: self.mappings.to_hash
                            })
    end
  end
end

インデックス名には環境情報をつけておきます。

理由はlocalでのテストをしやすくするためです。

index作成のrakeタスクを作成

続いてindexを作成するrake タスクを作ります。

まずはrakeタスクを作成するために、以下のコマンドをターミナルで実行します。

1
rails g task elasticsearch

作成されたrakeタスクを以下の様に変更します。

1
2
3
4
5
6
7
8
9
10
11
12
# lib/tasks/elasticsearch.rake
namespace :elasticsearch do
  desc 'Elasticsearch のindex作成'
  task :create_index => :environment do
    Article.create_index!
  end

  desc 'Article を Elasticsearch に登録'
  task :import_article => :environment do
    Article.import
  end
end

ではインデックスを作成します。

1
bundle exec rake elasticsearch:create_index

http://localhost:9200/_plugin/head/ にアクセスして「es_sample_article_development => info => Metadata」 の中身が以下の様になっていたら成功です!

ちなみに、curlコマンドでもマッピングを確認できます。 ターミナルで以下のコマンドを実行してみてください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
curl -XGET 'localhost:9200/es_sample_article_development/_mapping/article?pretty=true'
{
  "es_sample_article_development" : {
    "mappings" : {
      "article" : {
        "dynamic" : "false",
        "properties" : {
          "body" : {
            "type" : "string",
            "analyzer" : "kuromoji"
          },
          "title" : {
            "type" : "string",
            "analyzer" : "kuromoji"
          }
        }
      }
    }
  }
}

サンプルデータの作成

サンプルデータを作成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
# db/seeds.rb
ActiveRecord::Base.transaction do
  # ===========================
  # 記事(Article)
  # ===========================
  Article.delete_all
  10.times do |idx|
    Article.create!(
        title: "タイトル #{idx}",
        body: "本文 #{idx}"
    )
  end
end

Elasticsearchにデータを登録します。

1
2
3
4
5
# サンプルデータの生成
bundle exec rake db:seed

# Elasticsearchへの登録
bundle exec rake elasticsearch:import_article

http://localhost:9200/_plugin/head/ から「Structured Query」を選択して色々いじるとデータが格納されていることがわかると思います!

ちなみに、rails console からでも色々試せるのでぜひ色々遊んでみてください!

1
2
3
4
5
6
7
8
9
10
Article.search('9').results.count
#=> 1

Article.search('9').results.first
#=> #<Elasticsearch::Model::Response::Result:0x007fb7ebac0a08 @result=#<Hashie::Mash _id="10" _index="es_sample_article_development" _score=1.1972358 _source=#<Hashie::Mash body="本文 9" created_at="2016-01-03T11:02:46.578Z" id=10 title="タイトル 9" updated_at="2016-01-03T11:02:46.578Z"> _type="article">>


Article.search(query: {term: {title: "5"} }).records.first
#=> Article Load (0.1ms)  SELECT "articles".* FROM "articles" WHERE "articles"."id" = 6
#=> #<Article id: 6, title: "タイトル 5", body: "本文 5", created_at: "2016-01-03 11:02:46", updated_at: "2016-01-03 11:02:46">

とりあえず一覧の検索を実装

articles_controllerindex を以下のように変更。

1
2
3
4
5
6
7
8
9
10
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles =if params[:search]
                 Article.search(params[:search]).records
               else
                 Article.all
               end
  end
end

viewも以下を追加。

1
2
3
4
5
# 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 %>

http://localhost:3000/articles にアクセスすれば検索できると思います!


Special Thanks

おすすめの書籍