simple-rssで複数のフィードから情報を取得,そして問題発生
フィードを1つだけ処理するっていうのもおかしな話で,一般的には複数のフィードを一気に処理するもんだ.例えば,こんな感じに.
require 'rubygems' require 'simple-rss' require 'uri' require 'net/http' urls = ["http://japanese.engadget.com/rss.xml", "http://phpspot.org/blog/atom.xml", "http://uch-x40.seesaa.net/index.rdf", "http://d.hatena.ne.jp/omochist/rss", "http://shunlog.blog67.fc2.com/?xml"] uris = Array.new urls.each do |url| uris << URI.parse(url) end bodies = Array.new threads = Array.new uris.each do |uri| # 効率化を図るために,Threadで処理をする事にする threads << Thread.new(uri, bodies) do |u,b| Net::HTTP.start(u.host) do |http| path = u.path unless u.query.nil? path += '?' + u.query end b << http.request_get(path).body end end end threads.each do |t| t.join end feeds = Array.new # rsssってのも変だしfeedにした bodies.each do |body| feeds << SimpleRSS.new(body) end feeds.each do |feed| puts feed.title if feed.description puts feed.description else puts feed.tagline end end
最後の
feeds.each do |feed| puts feed.title if feed.description puts feed.description else puts feed.tagline end end
に違和感を感じざるを得ないけれども,これには諸事情がありまして.
問題点
複数のヴァージョンのフィード(たとえば,RDFとAtomをごっちゃに)を処理すると,厄介な事がありまして.
もしもAtomだけを処理するとなると,id:omochist:20060923:1158991617で示したNoMethodErrorの対処だけで,feed.descriptionでちゃんとtaglineの値が得られます.しかし,AtomとRDFをごっちゃに処理するとなると話は別.まずはコチラを見て頂きたい.
if $2 || $3 tag_cleaned = clean_tag(tag) eval %{ @#{ tag_cleaned } = clean_content(tag, $2, $3) } self.class.class_eval %{ attr_reader :#{ tag_cleaned } } end
これはSimpleRSS#parseの一部分.フィードにそのタグが存在し,且つ値があるのなら,インスタンス変数にそのタグの名前を追加し,読み込み用アクセサメソッド*1にそのタグを追加しておるんだけれど,このアクセサメソッドはクラスで共通になってしまうから,たとえAtomで読み込んだSimpleRSSのオブジェクトであったとしても,descriptionってアクセサができてしまって,descriptionを呼んでもNoMethodErrorが発生せずにnilが返ってしまい,method_missingが呼ばれない.
う〜ん,自分で書いてても,イマイチワケが分からん.フィーリングだよ.フィーリング.
でもちょっとおかしいと思うんだけど,インスタンス変数は普通のevalで作れるのに,アクセサメソッドはself.class.class_evalを呼ばないとダメってのはイマイチ理不尽な感じがする.
RailsでRSSリーダを作るとすれば
もしRSSリーダを作るとするのなら,各々のフィードの更新はコントローラでするべきか,それともモデルでするべきか.コントローラでフィードの更新を確認するのなら,必然的に全てのフィードで一気に更新する事になるであろう.しかし,もしモデル単体で更新するとすれば,チェックするフィードは一種類だけで済むので上のような問題は発生しない.
かと言って「更新」なんていうのを,本来"ただの門番"であるべきモデルに任せるっていうのもおかしな話で.
迷うな〜.
おまけ
どうしても最後の条件分岐が嫌と言うのなら,上のsimple-rss.rbの部分を,こういう風に書き換えれば良い.
if $2 || $3 tag_cleaned = clean_tag(tag) content_cleaned = clean_content(tag, $2, $3) eval %{ @#{ tag_cleaned } = content_cleaned def self.#{ tag_cleaned } return "#{ content_cleaned.to_s }" end } end
これでクラス共通のアクセサは作られず,そのインスタンスだけのアクセサが作られる.そのためNoMethodErrorが発生し,期待通りにmethod_missingを呼び出してくれる.
ただ,こうすると,せっかくcontent_cleanedでTimeのオブジェクトを作ってくれるのに,呼び出したらただの文字列になっちゃうので,あんまりお薦めできない.
ちなみに一番上のスクリプトの実行結果は,こうなる.
$ ruby somerss.rb phpspot開発日誌 PHPライブラリ紹介と最近のWEB技術情報をお届け UchのX40記 ThinkPad、最高だー! Engadget Japanese Engadget Japanese しゅんろぐ 今日も描き続ける山下しゅんやの生活 もち もち
はてなの鯖って,やっぱ重いのかな.
*1:造語.attr_readerのコトだと思って頂戴!