elasticsearch-rails gemを使ってみた

2016.02.23

丹内です。
掲題のとおり、railsで簡単にelasticsearchを使えるelasticsearch-railsを使ってみました。

ディレクトリ構成

$ tree -L 1
.
├── docker-compose.yml
├── rails
│   ├── Dockerfile
│   ├── Gemfile
│   ├── Gemfile.lock
│   ├── Procfile
│   ├── Rakefile
│   ├── app
│   ├── assets
│   ├── bin
│   ├── config
│   ├── config.ru
│   ├── db
│   ├── lib
│   ├── log
│   ├── public
│   ├── tmp
│   └── vendor
└── elasticsearch
    └── Dockerfile

環境情報

ruby 2.2.4
rails 4.2.5.1
docker 1.10.1

Elasticsearchコンテナ

公式イメージをベースに、kuromojiプラグインをインストールしてビルドします。 Dockerfileは以下です。

FROM elasticsearch:2.2.0

RUN plugin install analysis-kuromoji

Railsコンテナ

まずGemfileに以下を追記し、bundle installします。

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

elasticsearch-rails gemはActiveRecordを拡張する形で動きます。
要約すると、Elasticsearch::ModelをincludeしたActiveRecord::Base継承モデルにはimportメソッドが使えるようになり、それを使うことでElasticsearchにドキュメントを作成することができます。 なので、まずはModelを作成します。migrateも実行します。

class Article < ActiveRecord::Base
  include Elasticsearch::Model

  validates :message, presence: true

  index_name    "article-#{Rails.env}"
  document_type 'sample'
end

index_namedocument_typeでElasticsearchにドキュメントを作成する際の設定ができます。
あとは、どこかのクラスやJob Queue Workerの処理でArticle.importを実行されればOKです。
また、Elasticsearchの設定は、config/elasticsearch.ymlに記載します。今回はdocker-composeから環境変数でコンテナをlinkするので、環境変数から受け取れるようにしましょう。

network.host: <%= ENV['ELASTICSEARCH_URL'] | 'localhost' %>

なお、RailsアプリのDockerfileは以下のとおりです。

FROM alpine:3.3

ENV BUILD_PACKAGES="curl-dev ruby-dev build-base" \
    DEV_PACKAGES="zlib-dev libxml2-dev libstdc++ libxslt-dev tzdata yaml-dev mysql-client mysql-dev python py-pip python-dev" \
    RUBY_PACKAGES="ruby ruby-bigdecimal ruby-irb ruby-json ruby-rake ruby-io-console ruby-json yaml nodejs"

RUN apk --no-cache --update --upgrade add $BUILD_PACKAGES $RUBY_PACKAGES $DEV_PACKAGES && \
    gem install -N bundler

RUN gem install -N nokogiri -- --use-system-libraries && \
  gem install -N rails --version "$RAILS_VERSION" && \
  echo 'gem: --no-document' >> ~/.gemrc && \
  cp ~/.gemrc /etc/gemrc && \
  chmod uog+r /etc/gemrc && \

  bundle config --global build.nokogiri  "--use-system-libraries" && \
  bundle config --global build.nokogumbo "--use-system-libraries" && \
  find / -type f -iname \*.apk-new -delete && \
  rm -rf /var/cache/apk/* && \
  rm -rf /usr/lib/lib/ruby/gems/*/cache/* && \
  rm -rf ~/.gem

RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install

ADD . /myapp

EXPOSE 3000
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

コンテナ間連携

docker-compose.ymlで以下のように定義します。

rails:
  build: ./rails
  volumes:
    - ./rails:/myapp
  ports:
    - "3000:3000"
  links:
    - elasticsearch
  environment:
    ELASTICSEARCH_URL: elasticsearch
elasticsearch:
  build: ./elasticsearch
  ports:
    - "9200:9200"

先ほどconfig/elasticsearch.ymlに設定した環境変数は、このenvironmentで設定されます。
linkされたコンテナはhostsが編集されるため、コンテナのIPが動的に変わっても参照することができます。

動かしてみる

rails consoleで作業します。

$ docker-compose run rails c

まず、インデックスを作成します。

Loading development environment (Rails 4.2.5.1)

Frame number: 0/5
[1] pry(main)> Article.__elasticsearch__.create_index! force: true
[!!!] Index does not exist (Elasticsearch::Transport::Transport::Errors::NotFound)
=> {"acknowledged"=>true}

別な端末などで、インデックスの作成を確認します。

$ curl -XGET "$(docker-machine ip):9200/_aliases?pretty"
{
  "article-development" : {
    "aliases" : { }
  }
}

この時点ではドキュメントはありません。

$ curl -XGET 'localhost:9200/article-development/self/_search' -d '{"query":{"match_all":{}}}'
{"took":1,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":0,"max_score":null,"hits":[]}}

rails consleでモデルにデータを保存しimportします。

[2] pry(main)> Article.create(message: 'test')
[3] pry(main)> Article.import

Elasticsearchを見ると、ちゃんと保存されていることがわかります。

$ curl -XGET "$(docker-machine ip):9200/article-development/self/_search?pretty" -d '{"query":{"match_all":{}}}'
{
  "took" : 65,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "tweet-development",
      "_type" : "self",
      "_id" : "14",
      "_score" : 1.0,
      "_source" : {
        "id" : 1,
        "message" : "test",
        "created_at" : "2016-02-23T09:33:30.000Z",
        "updated_at" : "2016-02-23T09:33:30.000Z"
      }
    } ]
  }
}

※簡略化のため、出力の一部を編集しています。

まとめ

Railsから簡単にElasticsearchを使うことができる elasticsearch-rails gemを紹介しました。
ActiveRecord前提なので使いどころを考えることもあるかもしれませんが、このように手軽に使えることは魅力的だと思います。

参考URL