maeshimaの日記

メモ書きです

sunspotで検索する

Working with search - GitHubの意訳。

検索ことはじめ

Sunspot.search メソッドで検索できる。引数は検索で使う一つ以上のクラス(モデル)。オプションでブロックに検索条件を書ける。一番簡単な例としては、渡したクラスのインスタンス全部を検索する物がある

search = Sunspot.search(Post)

同時に二つ以上のクラスから検索したいときは、複数のクラスを引数として渡す

search = Sunspot.search(Post, Comment)

Sunspot::Rails を使っているなら、モデルの暮らすから直接 search メソッドを呼び出せる

search = Post.search

ブロック中で指定する検索条件については別の章で。この章では検索結果を扱う。

検索結果を得る

検索結果の情報を得たければ、hits メソッドを使う。hits メソッドは Sunspot::Search::Hit オブジェクトを返す。Sunspot::Search::Hit オブジェクトは検索結果の情報をカプセル化したもの。

実際のモデルオブジェクトの参照は、Hit#result を呼び出す。もし検索結果のメタデータ(関連度のスコア、geo distane, stored fields, キーワードハイライトなど)を気にしないなら results メソッドで直接検索したオブジェクトの配列にアクセスできる。

willpaginate を使う

will_paginate をロードしていたら、sunspot は自動で hits と results メソッドと will_paginate を統合する。テンプレートには下記のように書ける

<div class="pagination">
  <%= will_paginate(@search.hits) %>
</div>

検索時にページを指定するには下記のようにする

Sunspot.search(Post) do
  paginate(:page => params[:page])
end

メタデータ

実際の検索結果のインスタンスに加えて、Solr はいくつかの検索結果の情報を提供する。検索結果のクラスが持っているフィールドを設定していた場合、Solr はフィールドの値を返す。もしキーワードで検索していた場合は関連度のスコアを返す。下記の例で使っている each_hit_with_result メソッドは Hit オブジェクトと検索結果のモデルを返す便利なイテレータ

<div class="results>
  <% @search.each_hit_with_result do |hit, post| -%>
    <div class="result">
      <h2><%= hit.stored(:title) %></h2>
      <div class="score"><%= hit.score %></div>
      <p><%= h(post.body) %></p>
    </div>
  <% end -%>
</div>

Hit オブジェクトを扱うとき、実際 Hit で参照されるオブジェクトは result メソッドを呼ぶまでインスタンスが作られない。result メソッドは全ての Hit オブジェクトに検索結果を紐づける。格納されたフィールドを使うだけならインスタンスは必要ない。

キーワードハイライト

Hit オブジェクトに highlights メソッドがあり、その中にはハイライトされたフレーズが格納されている。ハイライトのフィールドを限定したい場合、オプションとしてフィールドの名前を引数として渡せる。 highlight メソッドは、フィールドの名前が引数として必須で、指定したフィールドの最初のハイライトが返ってくる。

Hightlight オブジェクトには format メソッドがあり、ブロック中にハイライトの文字列辺のフォーマットを指定できる。例

<ul class="search_results">
  <% @search.each_hit_with_result do |hit, post| -%>
    <li>
      <h3><%= h(post.title) %></h3>
      <p class="summary"><%= hit.highlight(:body).format { |fragment| content_tag(:em, fragment) } %></p>
    </li>
  <% end -%>
</ul>

この例では、ハイライトされたキーワードはタグで囲まれる。

不一致を検知して照合する

Solr は今のところリアルタイム検索を実装していない。言い換えると、書き込み頻度の多いアプリは常に最新のインデックスを参照することは出来ない。通常であれば、追加や更新の操作をした数秒〜数分でインデックスが同期される。削除の操作がすぐに同期されないのは問題。

Sunspot は不一致を推測する。Solr は DB に入っていないオブジェクトを検索結果として参照していても問題ない。results と each_hit_with_result メソッドを使った時、データベースにある結果を返す。データベースにない参照は捨てる。

hits メソッドはちょっと違って、デフォルトでは DB を触らずに Solr とだけやりとりする。DB に入っているデータだけを取得するようにちゃんとチェックしたい場合は、 :verify => true を hits メソッドの引数として渡す必要がある。

その他

検索に引っかかったインデックスの数は、Search#total メソッドで取得できる。

facets

facet は Search#facet メソッドで取得できる。

facet オブジェクトは rows メソッドを持っていて、FacetRow オブジェクトの配列を返す。FacetRow は count と value メソッドを持っている。

references オプションで設定をされたフィールドで、facets オブジェクトを作ることが出来る。facet は、そのプライマリキーで取得されたインスタンスを返す instance メソッドを持っている。hits メソッドは、facet インスタンスを lazy-load する。facet row の instance メソッドは lazy じゃない。

<div class="facets">
  <h3>Browse by Category</h3>
  <ul class="facet">
    <% for row in @search.facet(:category_ids).rows -%>
      <li><%= link_to(row.value, url_for(:category_id => row.value)) %> (<%= row.count %>)</li>
    <% end -%>
  </ul>
</div>