Rails + Cicindelaでレコメンデーション付ウェブサイトの構築(2)
目次
- ユーザ認証付Railsアプリの構築
- RailsアプリにCicindelaインタフェースを実装 ←いまここ
- Cicindelaの設定とバッチ処理設定
そんなわけで昨日作ったRuby on RailsアプリにCicindelaとのインタフェース機能をつけてみます。
Cicindelaインタフェース用クラスの作成
CicindelaはWeb APIが用意されており、アプリケーション側の実装言語に依存せずに利用できるので非常に便利です。今回のアプリの場合アクセスするタイミングは3ヶ所で、以下に例を示します。
(a) ユーザID100の人がアイテムID500のウェブページをブックマークしたとき
http://localhost/cicindela/record?set=bookmark&op=insert_pick&user_id=100&item_id=500
(b) ユーザID100の人がアイテムID500のウェブページのブックマークを削除したとき
http://localhost/cicindela/record?set=bookmark&op=delete_pick&user_id=100&item_id=500
(c) アイテムID500のウェブページの関連ページを表示するとき
http://localhost/cicindela/recommend?set=bookmark&op=for_item&item_id=500
これらのラッパークラスCicindelaIfを作ってapp/controllers/application.rbに書きます。
require 'net/http' class ApplicationController < ActionController::Base include AuthenticatedSystem helper :all protect_from_forgery class CicindelaIf @@host = 'localhost' @@service = 'bookmark' def self.set(user, item) # (a) path = '/cicindela/record?set=' + @@service + '&op=insert_pick&user_id=' + user.to_s + '&item_id=' + item.to_s res = Net::HTTP::get(@@host, path) end def self.del(user, item) # (b) path = '/cicindela/record?set=' + @@service + '&op=delete_pick&user_id=' + user.to_s + '&item_id=' + item.to_s res = Net::HTTP::get(@@host, path) end def self.get(item, max) # (c) path = '/cicindela/recommend?set=' + @@service + '&op=for_item&item_id=' + item.to_s begin res = Net::HTTP::get(@@host, path) rescue # レコメンデーションサーバが落ちている時は return [] # ガン無視して空配列を返す end unless /^(\d+\s+)+$/ =~ res # かなり適当なエラー検知 return [] end ary = res.split(/\s+/).map{|w| w.to_i} ary.slice(0 .. max-1) end end end
アプリケーションロジックへの組み込み
呼び出すタイミングは、(a)がBookmarksControllerクラスのcreate、(b)がdestroy、(c)がWebpagesControllerクラスのshowになります。
app/controllers/bookmarks_controller.rb(一部)
def create @webpage = Webpage.find_by_url(params[:webpage][:url]) if @webpage.nil? @webpage = Webpage.new(params[:webpage]) unless @webpage.save render :action => "new" end end new_bm = false @bookmark = Bookmark.find_by_user_id_and_webpage_id(current_user.id, @webpage.id) if @bookmark.nil? @bookmark = Bookmark.new(:user_id => current_user.id, :webpage_id => @webpage.id) new_bm = true end @bookmark.comment = params[:bookmark][:comment] if @bookmark.save CicindelaIf::set(current_user.id, @webpage.id) if new_bm # (a) flash[:notice] = 'Bookmark was successfully created.' redirect_to(bookmarks_url) else render :action => "new" end end def destroy @bookmark = Bookmark.find(params[:id]) @bookmark.destroy CicindelaIf::del(current_user.id, @webpage.id) # (b) redirect_to(bookmarks_url) end
app/controllers/webpages_controller.rb(一部)
def show @webpage = Webpage.find(params[:id]) @bookmarks = Bookmark.find_all_by_webpage_id(params[:id], :include => :user) # ランキングの上位5件取得 ranking = CicindelaIf::get(params[:id], 5) # (c) picks = Webpage.find(ranking) # ランキングの順番どおり並べ直して配列に格納 @recommends = [] ranking.each {|r| @recommends << picks.find{|w| w.id == r} } end
せっかくCicindelaから関連度の高い順にidのリストが返ってきたのに、Rails側でWebpage.find()でレコード取得するときに、
select * from webpages where id in (id1, id2, id3, ... )
というようなSQLになってしまい順番が失われるので並べ直しています。(もっとスマートな方法はないのかな?)
ビュー側では受け取ったものをそのまま表示するだけ。
app/views/webpages/show.html.erb(一部)
<h3>◆おすすめページ</h3> <% for rec in @recommends %> <%= link_to rec.title, rec.url %><br /> <% end %> <br />
ね?簡単でしょ。
とはいえWeb APIが正しく反応してくれないと、これだけでは何も表示されません。そんなわけで次回はCicindela側の設定について書く予定です。