Rails
Elasticsearch
elasticsearch-rails

ElasticSearch-Railsの導入手順・詰まった点など

RailsからElasticSearchを使いたい

初めてElasticSearchを使ったので、その際の手順や詰まった点などまとめてみました。

導入手順

Install

gem install elasticsearch-model
gem install elasticsearch-rails

既存のModelに反映させる

class Line < ActiveRecord::Base
  include Elasticsearch::Model
  ...

設定の作成

/config/initializers/elasticsearch.rb
config = {
    host: "http://localhost:9200/",
}

if File.exists?("config/elasticsearch.yml")
  config.merge!(YAML.load_file("config/elasticsearch.yml")[Rails.env].symbolize_keys)
end
Elasticsearch::Model.client = Elasticsearch::Client.new(config)
/config/elasticsearch.yml
development: &default
  host: 'http://localhost:9200/'
test:
  <<: *default
staging:
  <<: *default
production:
  host: 'http://XXX/'

ref) https://medium.com/@thecolorfulcrayon/configuring-elasticsearch-on-rails-8bcbe973e9e7

Rails Consoleで動作確認

[1] pry(main)> Line.__elasticsearch__.create_index!
=> {"acknowledged"=>true, "shards_acknowledged"=>true}
[2] pry(main)> Line.__elasticsearch__.import
  Line Load (1.0ms)  SELECT  `lines`.* FROM `lines`  ORDER BY `lines`.`id` ASC LIMIT 1000
=> 0
[3] pry(main)> Line.__elasticsearch__.search('秋田新幹線').results.first
=> #<Elasticsearch::Model::Response::Result:0x007fbdeccf3080
 @result=
  {"_index"=>"lines",
   "_type"=>"line",
...

悩んだ点

ElasticSearchをPrimary Data Storeに使用することの是非

結局Primary Data Storeとして使用しませんでしたが、下記サイトを参考にしました。

ネストされたデータの作成

/app/models/concerns/line_searchable.rb
module LineSearchable
  extend ActiveSupport::Concern

  included do
    include Elasticsearch::Model

    index_name    "line_#{Rails.env}"

    settings do
      mappings dynamic: 'false' do
        indexes :name, type: 'text'

        indexes :stations, type: :nested do
          indexes :id
          indexes :name, type: 'text'
        end
      end
    end

    def as_indexed_json(option = {})
      category_attrs = {
          id: self.id,
          name: self.name
      }
      category_attrs[:stations] = self.stations.map do |station|
        {
            id: station.id
            name: station.name
        }
      end

      category_attrs.as_json
    end
  end
end

ネストされたデータの検索

[1] pry(main)> query = {:query=>
  {:bool=>
    {:must=>
      [{"term"=>{"name"=>"JR中央線(快速)"}},
       {"nested"=>{"path"=>"stations", "query"=>{"terms"=>{"stations.id"=>[1, 2, 3]}}}}]}}}
[2] pry(main)> Line.search(query).results.first
...

includesやwhereをしてからImportしたい

コードを読み込んで行ったらoptionsに色々な機能を持たせていたので、それによって対応しました。

/app/models/concerns/line_searchable.rb
def self.elasticsearch_import
  self.__elasticsearch__.import(query: -> { includes(:stations).where('XXX') })
end

ref)
https://github.com/elastic/elasticsearch-rails/blob/b6d485748c71a07d064ea2a46a6da82d64a04cd7/elasticsearch-model/lib/elasticsearch/model/adapters/active_record.rb#L96

同一モデルでもindexやtypeを動的に変えたい

結局やりませんでしたが、使用する際にoptionsで動的にindexやtypeを指定することで対応可能かと思われます。

ref)
https://github.com/elastic/elasticsearch-rails/blob/master/elasticsearch-model/lib/elasticsearch/model/importing.rb#L102

ElasticSearchとactiverecord-importでNamespaceが被っている

[1] pry(main)> Line.import
ArgumentError: Invalid arguments!

activerecord-importが先に導入されていたこともあってactiverecord-import側のメソッド名を変えることはリスキーだったので、ElasticSearchのFunctionを呼ぶ際は__elastic_search__を経由することで対応しました。

[1] pry(main) > Line.__elastic_search__.import

参考