web-dev-qa-db-ja.com

Elasticsearch、Tire、およびNestedクエリ/ ActiveRecordとの関連付け

ElasticSearch with Tireを使用して、いくつかのActiveRecordモデルにインデックスを付けて検索しています。また、関連付けにインデックスを付けて検索するための「正しい」方法を探しています。このためのベストプラクティスのように思われるものが見つからなかったので、誰かが本当にうまくいくと思うアプローチを持っているかどうかを尋ねたいと思いました。

セットアップの例として(これは構成されていますが、問題を示しています)、章のある本があるとしましょう。各本には、タイトルと著者、そしてたくさんの章があります。各章にはテキストがあります。書籍のフィールドと章のテキストにインデックスを付けて、著者ごとに書籍を検索したり、特定の単語が含まれている書籍を検索したりできるようにします。

class Book < ActiveRecord::Base
  include Tire::Model::Search
  include Tire::Model::Callbacks

  has_many :chapters

  mapping do
    indexes :title, :analyzer => 'Snowball', :boost => 100
    indexes :author, :analyzer => 'Snowball'
    indexes :chapters, type: 'object', properties: {
      chapter_text: { type: 'string', analyzer: 'Snowball' }
    }
  end
end

class Chapter < ActiveRecord::Base
  belongs_to :book
end

それで私は検索をします:

s = Book.search do
  query { string query_string }
end

インデックス作成でうまくいくように見えても、それは機能しません。代わりにインデックスを作成する場合:

indexes :chapters, :as => 'chapters.map{|c| c.chapter_text}.join('|'), :analyzer => 'Snowball'

これによりテキストが検索可能になりますが、明らかにそれはナイスハックではなく、実際の関連オブジェクトを失います。次のような検索のバリエーションを試しました。

s = Book.search do
  query do
    boolean do
      should { string query_string }
      should { string "chapters.chapter_text:#{query_string}" }
    end
  end
end

そこにも運がない。 Tireを使用して関連するActiveRecordオブジェクトのインデックス作成と検索の優れた明確な例を誰かが持っている場合、それはここのナレッジベースへの本当に良い追加になると思われます。

アイデアや貢献をありがとう。

27
Masonoise

TireでのActiveRecordアソシエーションのサポートは機能していますが、アプリケーション内でいくつかの調整が必要です。図書館がここでより良い仕事をするべきであることは間違いありません、そして将来それは確かにそうなるでしょう。

そうは言っても、elasticsearchでRailsのアソシエーションを操作するためのタイヤ構成の本格的な例を次に示します。 active_record_associations.rb

ここでいくつか強調しておきます。

親に触れる

まず、関連付けの変更について、関連付けの親モデルに通知する必要があります。

Chapterに「属する」Bookモデルがある場合、次のことを行う必要があります。

class Chapter < ActiveRecord::Base
  belongs_to :book, touch: true
end

このように、次のようなことを行うと、次のようになります。

book.chapters.create text: "Lorem ipsum...."

bookインスタンスは、追加されたチャプターについて通知されます。

タッチへの対応

この部分を並べ替えたら、変更についてTireに通知し、それに応じてelasticsearchインデックスを更新する必要があります。

class Book < ActiveRecord::Base
  has_many :chapters
  after_touch() { tire.update_index }
end

(質問はありませんTireは、after_touch通知をそれ自体でインターセプトする必要があり、これを強制することはできません。一方、目を傷つけない方法でライブラリの制限を回避するのがいかに簡単であるかの証拠。)

Rails <3.1での適切なJSONシリアル化

READMEは、Rails <3.1で自動「JSONにルートキーを追加する」を無効にする必要があると述べていますが、多くの人はそれを忘れているので、含める必要がありますクラス定義でも:

self.include_root_in_json = false

Elasticsearchの適切なマッピング

今、私たちの仕事の要点が来ます-私たちのドキュメント(モデル)の適切なマッピングを定義します:

mapping do
  indexes :title,      type: 'string', boost: 10, analyzer: 'Snowball'
  indexes :created_at, type: 'date'

  indexes :chapters do
    indexes :text, analyzer: 'Snowball'
  end
end

titleにブースティング、created_atを「日付」、関連モデルの章テキストをインデックス付けしていることに注意してください。すべてのデータは、elasticsearchで単一のドキュメントとして効果的に「非正規化」されます(そのような用語がわずかに意味がある場合)。

適切なドキュメントのJSONシリアル化

最後のステップとして、elasticsearchインデックスでドキュメントを適切にシリアル化する必要があります。 ActiveRecordの便利なto_jsonメソッドをどのように活用できるかに注目してください。

def to_indexed_json
  to_json( include: { chapters: { only: [:text] } } )
end

このすべての設定が整ったら、ドキュメントのBookChapterの両方の部分のプロパティを検索できます。

active_record_associations.rb Rubyファイルを最初にリンクして実行して、全体像を確認してください。

詳細については、次のリソースを参照してください。

mapping/to_indexed_jsonの相互作用の詳細については、次のStackOverflowの回答を参照してください: ElasticSearch&Tire:Mappingとto_indexed_jsonの使用

このStackOverflowの回答を参照してください: ElasticSearch(Tire + ActiveRecord)のメソッドの結果にインデックスを付ける 関連のあるモデルにインデックスを付けるときにn +1クエリと戦う方法を確認してください。

51
karmi

私はこれを私のアプリケーションの1つでソリューションとして作成しました。これは、深くネストされたモデルのセットにインデックスを付けます。

https://Gist.github.com/paulnsorensen/4744475

更新:これを行うgemをリリースしました: https://github.com/paulnsorensen/lifesaver

3
paulnsorensen