- Sun×RECRUIT Mash up Award
- http://www.recruit.jp/mashup2006/
- アルバイト占い
- http://fortune.giovedi.net/
マッシュアップコンテストに応募しちゃった「アルバイト占い」のソースについて、一通りのリファクタリングが完了したので、えいやと公開してみる(ただし占いロジック部分だけは内緒(笑))。
リファクタリングといっても大げさなものではなく、見通しをよくして、共通部分をまとめ、占いロジックをカプセル化しただけ。
さすがにスケルトン的に使えるようなレベルではないが、お手軽に REST 系 API を使ってマッシュアップして遊ぼうという人には参考にしてもらえるかも?
見ればわかると思うが、「アルバイト占い」は ruby CGI で組まれている。
ruby CGI を採用したのは、個人的に一番開発速度が速いからだが(どうせ何百人何千人もアクセスするわけじゃないし)、たったこれだけソースで まがりなりにもマッシュアップアプリケーションしているのだから、開発が早いのも納得だろう(公開した範囲で一番コーディング量が多いのは、おまけの労働時間計算部……)。
ここをこうすればもっとうまく書けるとか、こうしないといけないなどの突っ込み大歓迎。
fortune.rb
#!/usr/bin/ruby -Kw # アルバイト占い (c)v914AKSTN # mashup-ed with Amazon Web Services API and FromA API require "cgiutil.rb" require "restutil.rb" require "fortunelogic.rb" # データディレクトリ(インクルードファイル、テンプレート、占いデータ&ログ) DATADIR = (ENV['RUBYLIB'] || ".") + "/" # 定数 AWS_KEY = "Amazon アクセスキー" FROMA_KEY = "フロムエー アクセスキー" AWS_RES = "ItemIds,ItemAttributes,EditorialReview,Reviews,SalesRank,Offers" # CGI 初期化 cgi = CGI.new print "Content-Type: text/html\n\n" # 占いロジック fortune = Fortune.new(DATADIR) # 入力パラメータチェック asin = cgi["asin"] if asin !~ /^[0-9A-Z]{10}$/ puts eval(readTemplate(DATADIR + "param-error.html")) exit 1 end # Amazon 接続&レスポンス解析 path = "/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=#{AWS_KEY}&Operation=ItemLookup&ItemId=#{asin}&ResponseGroup=#{AWS_RES}&Version=2006-06-07" doc = getResponseAsDom('webservices.amazon.co.jp', path) product = doc.elements['ItemLookupResponse/Items/Item'] if doc if !product puts eval(readTemplate(DATADIR + "amazon-error.html")) exit 1 end price = fortune.parseAmazon(product) # 検索条件構成&フロムエー接続 froma_path = "/s/r/jobSearch.jsp?api_key=#{FROMA_KEY}" fortune.getCondition.each do |key, value| froma_path += "&#{key}=#{value}" end doc = getResponseAsDom('xml.froma.com', froma_path) # 占いの結果として、<Offer> エレメントを得る result = fortune.divine(doc) if !result puts eval(readTemplate(DATADIR + "param-error.html")) exit 1 end # 労働時間計算 paytext = result.elements['PayText'].text # 給料 worktime = nil if paytext =~ /時給.*?([\d0123456789]{3,})円/ pay = to_i_ex($1) fortune.logger("時給: #{pay}") if price>0 && pay>0 day, hour, min, sec = fractionalDate(1.0 * price / pay / 24.0, 24) worktime = "#{hour}時間 #{min}分 #{sec}秒" worktime = "#{day}日 #{worktime}(寝ないで)" if day > 0 end elsif paytext =~ /月給(\d+)円/ pay = $1.to_i fortune.logger("月給: #{pay}") if price>0 && pay>0 day, hour, min, sec = fractionalDate(21.5 * price / pay, 8) worktime = "#{day}日 #{hour}時間 #{min}分 #{sec}秒" end end fortune.logger("#{paytext} : #{worktime}") # 占い結果出力 puts eval(readTemplate(DATADIR + "view.html"))
fortunelogic.rb
#!/usr/bin/ruby -Kw # アルバイト占い (c)v914AKSTN # 占いロジック(abstract。でも一応動くはず。本物はもっとごちゃごちゃしてます) require 'logger' class Fortune def initialize(datadir) @log = Logger.new(datadir + 'fortune.log') end # Amazon 検索結果から、商品の価格を取得して返す def parseAmazon(product) _price = product.elements['Offers/Offer/OfferListing/Price/Amount'] if _price && _price.text then _price.text.to_i else 0 end end # フロムエー API 検索条件を HashMap で返す def getCondition {'ksjcd'=>'04', 'shrt_indx_cd'=>'1003'} # 短期・1週間 end # フロムエー API 検索結果から、占いの結果として <Offer> エレメントを1つ返す def divine(doc) doc.elements.to_a('OfferList/Offer')[0] end def logger(st) @log.debug(st) end end
restutil.rb
#!/usr/bin/ruby -Kw # REST型APIへのアクセス require 'net/http' require 'rexml/document' Net::HTTP.version_1_2 # 指定URLにアクセスし、レスポンスを DOM にパースして返す def getResponseAsDom(host, path) doc = nil Net::HTTP.start(host) do |http| response = http.get(path) doc = REXML::Document.new(response.body) end doc end # 指定URLのマップにアクセスし、レスポンスを DOM にパースし、マップに格納して返す。 # RSS取得用。 def getResponseListAsDom(host, pathlist) list = Hash.new pathlist.each do |key, path| list[key] = getResponseAsDom(host, path) sleep 1 end list end
cgiutil.rb
#!/usr/bin/ruby -Kw require 'cgi' # html無害化 def h(st) CGI::escapeHTML(st) end # テンプレートファイル読み込み # html 内に埋め込み値を #{} で記述、eval するだけの超簡易テンプレート…… def readTemplate(filename) "<<HTMLTEMPLATE\n" + File.read(filename) + "\nHTMLTEMPLATE" end # 全角半角混じりの数値文字列を整数に変換する # tr() が2バイト文字にも対応していれば良かったのだが。 def to_i_ex(num) num.gsub('0','0').gsub('1','1').gsub('2','2').gsub('3','3').gsub('4','4').gsub('5','5').gsub('6','6').gsub('7','7').gsub('8','8').gsub('9','9').to_i end # 日時を表す実数値(1.0=1日)を日・時・分・秒に分解する def fractionalDate(_date, hours_of_day) day = _date.floor _tmp = (_date - day) * hours_of_day hour = _tmp.floor _tmp = (_tmp - hour) * 60 min = _tmp.floor sec = ((_tmp - min)*60).floor [day,hour,min,sec] end