Railsコードリーディング

第6回 全文検索を実装したソースコードを読もう

倉貫 義人
松村 章弘
TIS株式会社
SonicGarden

2009/9/3

icon 検索フェイズのコードを読んでみよう

 最後に実際にユーザーが検索操作を行ない、HyperEstraierにアクセスして情報を取得し、ユーザーに適切な情報を表示するフェイズを見ていきます。ここは実際に動作を確認できますので、デモサイトを見ていただくとよいでしょう。

 デモサイトにアクセスして、ログインしてください。右上に全文検索のための検索ボックスが表示されています。ここに、「SKIP」と入力し検索ボタンを押してみます。

 次の画面で、検索結果が表示されるはずです。検索結果は1画面に、最大10件まで表示できますが、おそらくそれより少ない件数が表示されているのではないでしょうか。

 これは、HyperEstraierから得た「SKIP」という検索キーワードに該当した検索結果から、ログインしているユーザーが閲覧することができない情報をフィルタリングしているためです。

 この動作がSKIPの全文検索の一連の流れです。検索結果が1画面で10件でなく中途半端な件数が表示されるのは、ほかの検索サイトなどと比較すると不思議な感じがするかもしれません。しかし、実装をシンプルにするためにもこのような仕様にしています。それでは、動きが分かったのでソースコードを追ってみましょう。

 まず、ユーザーが検索操作を行う部分を見ていきます。検索結果が表示されるURLは以下のようなURLです。

/search/full_text_search?query=SKIP&commit=全文検索&target_aid=all&searcher=hyperestraier

 これによって実行されるのは、SearchControllerのfull_text_searchアクションです。

●app/controllers/search_controller.rb
93   #全文検索
94   def full_text_search
95     @main_menu = @title = '全文検索'
96
97     params[:target_aid] ||= "all"
98     params[:query] = params[:full_text_query] unless params[:full_text_query].blank?
99     params[:per_page] = 10
100    params[:offset] ||= 0
101
102    search = Search.new(params, current_user.belong_symbols_with_collaboration_apps)
103    if search.error.blank?
104      # TODO: インスタンス変数に代入することなく@searchで画面表示
105      @invisible_count = search.invisible_count
106      make_instance_variables search.result
107    else
108      # Searchクラスのメッセージの国際化
109      N_("Please input search query.")
110      N_("Access denied by search node. Please contact system owner.")
111      @error_message = search.error
112    end
113  end

 95行目では、ビューで表示する際に必要な変数を定義しています。97行目から100行目では、検索の条件の初期値などを設定しています。

 102行目がこのコントローラのメインの操作である、Searchクラスにパラメータと所属しているユーザーの閲覧権限の情報を渡してインスタンス化している部分です。そして、Seachクラスのインスタンスにエラーがあるかどうかを確認して、エラーがない場合は表示のための処理を行って表示します。エラーがある場合は、エラーの内容を変数に設定しています。

 104行目で「TODO」と書いているのは、今後のリファクタリングのポイントです。現状、動作しているのですが、より分りやすいコードにするためにここは直しておくべきだと記録しています。このように書いておくと、rakeコマンドのnotesを実行して得られる一覧でTODOを確認できます。

 まだHyperEstraierにアクセスしていないので、もう少し奥のクラスまで見ていきましょう。SearchControllerから呼びだされているSearchクラスを見てみます。

●lib/search.rb
21   def initialize params, publication_symbols
22     unless params[:query] && !SkipUtil.jstrip(params[:query]).empty?
23       @error = NO_QUERY_ERROR_MSG
24     else
25       est_result = HyperEstraier.new(params)
26       if error_message = est_result.error
27         @error = error_message
28       else
29         @result = est_result.result_hash
30         new_result = self.class.remove_invisible_element(@result[:elements], publication_symbols)
31         @invisible_count = @result[:elements].size - new_result.size
32         @result[:elements] = new_result
33       end
34     end
35   end

 SearchControllerでインスタンス化されると、21行目のinitializeメソッドに処理が渡ります。

 ここでは、22行目から24行目で検索キーワードが正しく渡ってきているか確認を行います。もし、検索キーワードがない場合は処理できないのでエラーとして返します。

 25行目では、さらにHyperEstraierのクラスを呼び出して検索のパラメータを渡しています。ここで返ってきたオブジェクトにエラーがある場合はエラー処理を行います(26、27行目)。エラーがない場合、ここで返ってきた結果には、閲覧権限によらずすべての情報が含まれているためフィルタリングを行っていきます。

 30行目でremove_invisible_elementを呼び、閲覧できない情報を取り除いています。31行目では取り除いた件数を調べて変数に設定し、32行目では取り除いた結果を変数に代入しています。このようにして、検索結果にフィルタリングをかけたオブジェクトをSearchControllerに返します。

 さて、Searchクラスを見てみましたが、実際にHyperEstraierのインデックスに情報を問い合わせている部分はまだ先でした。これは、オープンソース化前にGoogleのアプライアンスを全文検索のバックエンドに用いることもできるようにしていたため、Searchクラスのバックエンドがさらに分割されているのです。

 現在はHyperEstraierのみをエンジンとして利用していますが、次に読むHyperEstraierクラスを置き換ることで、ほかのエンジンも利用可能になっています。

 それでは、HyperEstraierのクラスを読んでみましょう。

●lib/search/hyper_estraier.rb
16 require "estraierpure"
17 
18 class Search
19   class HyperEstraier
20     ACCESS_DENIED_ERROR_MSG = "Access denied by search node. Please contact system owner."
21     include EstraierPure
22     attr_reader :offset, :per_page, :error, :result_hash
23 
24     def initialize params
25       @result_hash = {
26         :header => { :count => -1, :start_count => 0, :end_count => 0, :prev => "", :next => "", :per_page => 10 },
27         :elements => []
28       }
29       @per_page = (params[:per_page] || 10).to_i
30       @offset = (params[:offset] || 0).to_i
31 
32       node = self.class.get_node
33       cond = self.class.get_condition(params[:query], params[:target_aid], params[:target_contents])
34       if nres = node.search(cond, 1)
35         @result_hash[:header] = self.class.get_result_hash_header(nres.hint('HIT').to_i, @offset, @per_page)
36         @result_hash[:elements] = self.class.get_result_hash_elements(nres, @offset, @result_hash[:header][:end_count])
37       else
38         # ノードにアクセスできない場合のみ nres は nil
39         ActiveRecord::Base.logger.error "[HyperEstraier Error] Connection not found to #{INITIAL_SETTINGS["estraier_url"]}"
40         @error = ACCESS_DENIED_ERROR_MSG
41       end
42     end

 HyperEstraierクラスでは、21行目でEstraierPureをインクルードしています。これは、HyperEstraierに付属しているRubyからHyperEstraierのインデックスを操作するためのライブラリです。HyperEstraierからの検索などが比較的楽に実装できます。

 それでは、ソースを読んでみましょう。Searchクラスからインスタンス化されると、24行目のinitializeメソッドが呼び出されます。25行目から30行目では、パラメータの情報などからインスタンス変数の初期値を設定しています。

 32行目では、HyperEstraierのインデックスのオブジェクトを取得して、nodeに代入しています。get_nodeの中では先ほどインクルードしたEstraierPureにより提供されているクラスを利用しています。

 33行目では、検索条件のオブジェクトを作成し、condに代入しています。nodeとcondを利用して実際に、34行目で検索を実行します。nodeのsearchメソッドの返り値には、HyperEstraierからの検索の結果が入っています。これが検索の部分です。

 結果の取得に成功した場合は、この返り値によりインスタンス変数を更新します。このとき、メタ情報にアクセスしてコンテンツの閲覧権限の情報などを取得しています。もし、nilが返ってきた場合は、インデックスにから情報の取得に失敗しているためエラーとしてインスタンス変数を設定します。

 このように、HyperEstraierクラス内では、実際にHyperEstraierのインデックスへのアクセスを行い、検索結果を取得します。

 これが、ユーザーの検索操作によりHyperEstraierにアクセスして、ユーザーに結果を表示する処理の流れです。ほかのフェイズに比べて処理は長かったと思いますが、HyperEstraierとの連携の方法などを理解できたのではないかと思います。

◇ ◆ ◇

 今回は、HyperEstraierという全文検索のエンジンと連携したSKIPの特徴を活かした全文検索について、機能の目的から実装までを紹介しました。SKIPはこのように各機能にこだわりを持って設計、実装しています。その一部を感じていただけたのではないでしょうか。

 今回で、全6回のRails製社内SNS のSKIPを利用したソースコードリーディングは最終回となります。連載としてはこれで終了しますが、今後ともSKIPは改良を続けよりよいコードに成長してきます。

 これからも、Railsのコードの参考にSKIPのソースコードを利用していただければと思います。ソースコードに関する疑問があれば、気軽にSUG(SKIPユーザグループ)まで質問いだだければと思います。

 最後まで、読んで頂きありがとうございました。また、コードでお会いしましょう。

prev
4/4
 

Index
全文検索を実装したソースコードを読もう
  Page1
全文検索機能で情報の収集を便利に実現
SKIPの全文検索のアーキテクチャ
全文検索エンジン「HyperEstraier」
  Page2
全文検索キャッシュの仕組み
全文検索の動作の流れ
SKIPのソースコード入手方法
  Page3
キャッシュ生成フェイズのコードを読んでみよう
収集フェイズのコードを読んでみよう
Page4
検索フェイズのコードを読んでみよう

index Railsコードリーディング 〜scaffoldのその先へ〜

 Ruby/Rails関連記事
プログラミングは人生だ
まつもと ゆきひろのコーディング天国
 ときにプログラミングはスポーツであり、ときにプログラミングは創造である。楽しいプログラミングは人生をより実りあるものにしてくれる
生産性を向上させるRuby向け統合開発環境カタログ
Ruby on Rails 2.0も強力サポート
 生産性が高いと評判のプログラミング言語「Ruby」。統合開発環境を整えることで、さらに効率的なプログラミングが可能になる
かんたんAjax開発をするためのRailsの基礎知識
Ruby on RailsのRJSでかんたんAjax開発(前編)
 実はAjaxアプリケーション開発はあなたが思うよりも簡単です。まずはRuby on Railsの基礎知識から学びましょう
Praggerとnetpbmで作る画像→AA変換ツール
Rubyを使って何か面白いものを作ってみよう!
 一般的な画像をアスキーアートに変換するツールを作ってみる。さらに出力にバリエーションを持たせてみよう
コードリーディングを始めよう
Railsコードリーディング〜scaffoldのその先へ〜(1)
 優れたプログラマはコードを書くのと同じくらい、読みこなす。優れたコードを読むことで自身のスキルも上達するのだ
  Coding Edgeフォーラムフィード  2.01.00.91


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間