Rails - sunspot で全文検索をする(1)
rails に sunspot という全文検索のプラグインがあります。 solrという検索エンジンを使っており、モデルに数行コードを追加するだけで、インデックスを動的に作成されます(再構築も可)
https://github.com/sunspot/sunspot
準備
Gemfile に以下を追加
gem "sunspot-rails"
gem "sunspot-solr"
bundle コマンドで実行
$ bundle
設定ファイル(config/sunspot.yml)を作成
$ rails generate sunspot_rails:install
自動生成された config/sunspot.yml は次の通り。
production:
solr:
hostname: localhost
port: 8983
log_level: WARNING
# read_timeout: 2
# open_timeout: 0.5
development:
solr:
hostname: localhost
port: 8982
log_level: INFO
test:
solr:
hostname: localhost
port: 8981
log_level: WARNING
solr の起動する・終了する
so起動する
$ bundle exec sunspot-solr start -p 8982
8982 はRAILS_ENV=developmentの場合のポート番号です。config/sunspot.yml の port にあわせます。
終了する
$ bundle exec sunspot-solr stop
モデルを編集
はじめに
scaffold で次のモデルを作ったと想定します。
$ rails g scaffold post title:string body:text blog_id:integer author_id:integer
$ rails g scaffold blog title:string
$ rails g scaffold author name:string email:string
モデルに追加
検索対象は Post モデルです。 タイトルと本文にインデックスを作成します。 searchable メソッドでインデックスの対象を記述します。 text で全文検索対象の属性を指定します。 integer, double, time の属性も指定できます。こちらは範囲指定検索の対象となるようです。
class Post < ActiveRecord::Base
searchable do
text :title
text :body, :stored => true
end
end
body属性に stored というオプションがついていますが、これはインデックスに値を保存するもののようです(スニペット中の検索ワードを強調表示するために使うhighlight機能で必須)。
テストデータを作成する
scaffold により作成された画面またはデータベースにデータを設定します あいうえお、なんかよりは、適当なニュース記事のタイトルと本文にするとおもしろいです
レコード挿入時に Errno::ECONNREFUSED
というエラーが出た場合は、solrが起動していないかポート番号が間違ってます
検索を実行する
感覚を掴むため rails console で遊んでみます。
Postモデルの'Google'というワードで全文検索をした例です
$ rails console
Loading development environment (Rails 4.0.0)
2.0.0p247 :001 > search = Post.search do
2.0.0p247 :002 > fulltext 'Google'
2.0.0p247 :003?> end
次のような値が返されればOKです。
D, [2013-08-27T21:59:32.515315 #23093] DEBUG -- : SOLR Request (29.5ms) [ path=# parameters={data: fq=type%3APost&q=Google&fl=%2A+score&qf=title_text+body_texts&defType=dismax&start=0&rows=30, method: post, params: {:wt=>:ruby}, query: wt=ruby, headers: {"Content-Type"=>"application/x-www-form-urlencoded; charset=UTF-8"}, path: select, uri: http://localhost:8982/solr/select?wt=ruby, open_timeout: , read_timeout: , retry_503: , retry_after_limit: } ]
=> <Sunspot::Search:{:fq=>["type:Post"], :q=>"Google", :fl=>"* score", :qf=>"title_text body_texts", :defType=>"dismax", :start=>0, :rows=>30}>
これだけだと検索結果がわかりません。結果を取得するには2種類のメソッドがあります。
hits
検索ワードに一致した結果のインデックス情報(Sunspot::Search::Hit)のみを返します。 データベースを検索しないので、インデックスに含まれていない属性値にはアクセスできません。
2.0.0p247 :004 > search.hits
=> [#<Sunspot::Search::Hit:Post 3>, #<Sunspot::Search::Hit:Post 2>]
2件ヒットしているのがわかります。Sunspot::Search::Hit:Post #
の # は検索したデータの :id となっているようです。
results
検索ワードに一致した結果のインデックス情報をもとにデータベースからロードした値を返します。 モデルが全てロードされた状態ですが、データベースを検索するので遅くメモリを消費します。
2.0.0p247 :009 > search.results
DEPRECATION WARNING: Relation#all is deprecated. If you want to eager-load a relation, you can call #load (e.g. `Post.where(published: true).load`). If you want to get an array of records from a relation, you can call #to_a (e.g. `Post.where(published: true).to_a`). (called from irb_binding at (irb):9)
Post Load (2.2ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` IN (3, 2)
=> [#<Post id: 3, title: "Googleの新プロトコルQUICを試す", body: "以前、「Googleが仕掛ける新プロトコルQUICとは何か」のブログエントリーを書いたのが2月末の事で...", blog_id: nil, author_id: nil, created_at: "2013-08-27 11:41:42", updated_at: "2013-08-27 11:41:42">, #<Post id: 2, title: "【製品リリース】Google™ から誕生した新しいスマートフォン「Nexus 4」、日本市場に向け8月...", body: "LG エレクトロニクス(本社:韓国, www.lge.com)の日本法人LG エレクトロニクス・ジャパ...", blog_id: nil, author_id: nil, created_at: "2013-08-27 11:40:35", updated_at: "2013-08-27 11:40:35">]
こちらは Postのオブジェクトが2つ返されています。
なお、マッチしたサンプルデータは、はてなブックマークに掲載されていた次のデータを使いました。
- http://d.hatena.ne.jp/jovi0608/20130628/1372408950 ぼちぼち日記
- http://www.lg.com/jp/press-releases/20130827nexus4 LG
each_hit_with_result
インデックス情報とデータベースからロードしたオブジェクト両方を取得します。 何かと便利です。。
明日コントローラとビューに組み込んでみた結果を報告してみます。
Ruby on RailsによるWebアプリケーション・スーパーサンプル
- 作者: 久保秋真,後藤修一,中村真一郎
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2008/03/26
- メディア: 大型本
- 購入: 4人 クリック: 234回
- この商品を含むブログ (21件) を見る