検索フェイズのコードを読んでみよう
最後に実際にユーザーが検索操作を行ない、HyperEstraierにアクセスして情報を取得し、ユーザーに適切な情報を表示するフェイズを見ていきます。ここは実際に動作を確認できますので、デモサイトを見ていただくとよいでしょう。
デモサイトにアクセスして、ログインしてください。右上に全文検索のための検索ボックスが表示されています。ここに、「SKIP」と入力し検索ボタンを押してみます。
次の画面で、検索結果が表示されるはずです。検索結果は1画面に、最大10件まで表示できますが、おそらくそれより少ない件数が表示されているのではないでしょうか。
これは、HyperEstraierから得た「SKIP」という検索キーワードに該当した検索結果から、ログインしているユーザーが閲覧することができない情報をフィルタリングしているためです。
この動作がSKIPの全文検索の一連の流れです。検索結果が1画面で10件でなく中途半端な件数が表示されるのは、ほかの検索サイトなどと比較すると不思議な感じがするかもしれません。しかし、実装をシンプルにするためにもこのような仕様にしています。それでは、動きが分かったのでソースコードを追ってみましょう。
まず、ユーザーが検索操作を行う部分を見ていきます。検索結果が表示されるURLは以下のようなURLです。
これによって実行されるのは、SearchControllerのfull_text_searchアクションです。
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クラスを見てみます。
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のクラスを読んでみましょう。
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ユーザグループ)まで質問いだだければと思います。
最後まで、読んで頂きありがとうございました。また、コードでお会いしましょう。
4/4 |
Index | |
全文検索を実装したソースコードを読もう | |
Page1 全文検索機能で情報の収集を便利に実現 SKIPの全文検索のアーキテクチャ 全文検索エンジン「HyperEstraier」 |
|
Page2 全文検索キャッシュの仕組み 全文検索の動作の流れ SKIPのソースコード入手方法 |
|
Page3 キャッシュ生成フェイズのコードを読んでみよう 収集フェイズのコードを読んでみよう |
|
Page4 検索フェイズのコードを読んでみよう |
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) 優れたプログラマはコードを書くのと同じくらい、読みこなす。優れたコードを読むことで自身のスキルも上達するのだ |
|
- プログラムの実行はどのようにして行われるのか、Linuxカーネルのコードから探る (2017/7/20)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。最終回は、Linuxカーネルの中では、プログラムの起動時にはどのような処理が行われているのかを探る - エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね? (2017/7/13)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、プログラムの終わりに呼び出されるexit()の中身を探る - VBAにおけるFileDialog操作の基本&ドライブの空き容量、ファイルのサイズやタイムスタンプの取得方法 (2017/7/10)
指定したドライブの空き容量、ファイルのタイムスタンプや属性を取得する方法、FileDialog/エクスプローラー操作の基本を紹介します - さらば残業! 面倒くさいエクセル業務を楽にする「Excel VBA」とは (2017/7/6)
日頃発生する“面倒くさい業務”。簡単なプログラミングで効率化できる可能性がある。本稿では、業務で使うことが多い「Microsoft Excel」で使えるVBAを紹介する。※ショートカットキー、アクセスキーの解説あり
|
|