ども、@kimihom です。
前回の Elasticsearch Rails の記事で、"サジェスト機能を実現する"とあったが補足事項があったので追記する。
ハマった問題
前回の記事を元に、実際に以下の rake タスクを実行すれば、確かにインポートしたデータはサジェストしてくれる。
bundle exec rake environment elasticsearch:import:all FORCE=y
しかし、なぜかモデルを更新するとその更新した値がサジェストとして出てこなくなる問題が起きることがわかった。つまり、前回の記事の状態だとサジェストとして機能するのは一括インポートしたデータと、新規作成したデータのみが正しく表示されることになってしまう。これではマズイ。
問題の原因
こりゃあおかしい、と Elasticsearch Rails のソースコードを読むと、update_documentメソッドが以下の通りになっていた。
def update_document(options={}) if changed_attributes = self.instance_variable_get(:@__changed_attributes) attributes = if respond_to?(:as_indexed_json) self.as_indexed_json.select { |k,v| changed_attributes.keys.map(&:to_s).include? k.to_s } else changed_attributes end client.update( { index: index_name, type: document_type, id: self.id, body: { doc: attributes } }.merge(options) ) else index_document(options) end end
つまり、更新したフィールドだけが Elasticsearch の更新対象となり、サジェストの部分が更新されないような作りになっていたのだ。とはいえ、元の elasticsearch-rails のソースを書き換えることはしたくないので、 merge(options)
をうまく利用することで解決した。ちなみに上記の self
は__elasticsearch__
であることに注意。
問題の解決
Elasticsearch-rails のコールバックをカスタムコールバックにし、以下のようなコードに変更した。以下は自分の Rails ソースのモデル内のコード。
after_commit on: [:create] do __elasticsearch__.index_document end after_commit on: [:update] do if changed_attributes = __elasticsearch__.instance_variable_get(:@__changed_attributes) hash = self.as_indexed_json.select { |k,v| changed_attributes.keys.map(&:to_s).include? k.to_s } doc = { suggest_field: {input: [self.name, self.email, self.address]}}.merge(hash) __elasticsearch__.update_document({body: {doc: doc}}) end end after_commit on: [:destroy] do __elasticsearch__.delete_document end
これにより、include Elasticsearch::Model::Callbacks
は消すことになる。doc = { suggest_field: {input: [self.name, self.email, self.address]}}.merge(hash)
を追加したいがためにこのようなコードになった。何はともあれ、これでモデルを更新した時にサジェストの方も無事に更新されるようになった。
終わりに
import によってサジェストがしっかりと動いていたから、アップデートの時だけ動かないのはソースを読めば確実に直せると思ったので、今回はいきなりソースを読んで解決した。
落としてきたgemソースから grep すれば簡単に対象の場所は見つかるし、困った時はソースを読んで解決できる力はあるととても便利だと感じる。
こういうトリッキーなことしちゃうと今後のGemアップデートで動かなくなる可能性が高くなるから、それだけ注意しないといけない。ということでソースを読んでハックするのはできる限り控えめにしていきたいところだ。