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

に違和感を感じざるを得ないけれども,これには諸事情がありまして.

問題点

複数のヴァージョンのフィード(たとえば,RDFAtomをごっちゃに)を処理すると,厄介な事がありまして.
もしもAtomだけを処理するとなると,id:omochist:20060923:1158991617で示したNoMethodErrorの対処だけで,feed.descriptionでちゃんとtaglineの値が得られます.しかし,AtomRDFをごっちゃに処理するとなると話は別.まずはコチラを見て頂きたい.

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を呼ばないとダメってのはイマイチ理不尽な感じがする.

RailsRSSリーダを作るとすれば

もし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のコトだと思って頂戴!