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として使用しませんでしたが、下記サイトを参考にしました。
- データベースとしてのElasticsearch
- Elasticsearch as a NoSQL Database
- Elasticsearch 2.3 as primary data store?
ネストされたデータの作成
- Elasticsearch Nested Type vs Array Objects
/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
ネストされたデータの検索
- 【基礎編】Elasticsearchの検索クエリを使いこなそう
[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
同一モデルでもindexやtypeを動的に変えたい
結局やりませんでしたが、使用する際にoptionsで動的にindexやtypeを指定することで対応可能かと思われます。
ElasticSearchとactiverecord-importでNamespaceが被っている
- activerecord-importとelasticsearch-railsでメソッドが被る問題
[1] pry(main)> Line.import
ArgumentError: Invalid arguments!
activerecord-importが先に導入されていたこともあってactiverecord-import側のメソッド名を変えることはリスキーだったので、ElasticSearchのFunctionを呼ぶ際は__elastic_search__
を経由することで対応しました。
[1] pry(main) > Line.__elastic_search__.import
参考
- elasticsearch-rails検証
- elasticsearch-rails運用
- Elasticsearchを使ったRailsサンプルアプリケーションの作成