WebAPIから検索結果を取得。

ってことでRoRで使ってみようと思い、ActiveRecordに影響を受けつつ作りました。
エラー処理はいれてません。
どなたかDSL風な実装の方法を教えてください・・。


WebApi::Base

#!/usr/bin/ruby -Ku
require "rexml/document"
require "open-uri"
require "uri"

module WebApi
  #
  # Active_Support
  #
  def self.underscore(str)
    str.gsub(/::/, '/').
      gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
      gsub(/([a-z\d])([A-Z])/,'\1_\2').
      tr("-", "_").
      downcase
  end

  class Base
    def initialize(options = { })
      @options = options
    end

    def query
      params = @options.keys.map {|key| "#{key}=#{URI.escape(@options[key])}" }.join('&')
      "#{api_url}?#{params}"
    end

    def doc
      @doc ||= REXML::Document.new(open(query).read).root
    end

    def items
      items = []
      doc.elements.each(doc_item) do |item|
        items << parse_item(item, Item.new)
      end
      items
    end

    private
    def parse_item(item, result)
      item.elements.each do |e|
        parse_item(e, result) if e.has_elements?
        result.add(e) if e.has_text?
      end
      result
    end

    def self.url(api_url)
      module_eval("def api_url; '#{api_url}'; end")
    end

    def self.items(doc_item)
      module_eval("def doc_item; '#{doc_item}'; end")
    end

    def self.single(*args)
      args.each do |arg|
        module_eval("def #{WebApi.underscore(arg)}; doc.elements['#{arg}'].text; end")
      end
    end

    public
    def self.find(hash)
      self.new(hash).items
    end
  end

  class Item
    def add(element)
      instance_eval "def #{WebApi.underscore(element.name)}; '#{element.text}'; end"
    end
  end
end

使い方(例:ホットペッパー

class Hotpepper < WebApi::Base
  url "http://api.hotpepper.jp/GourmetSearch/V1/"
  items "Shop"
# single "NumberOfResults", "DisplayPerPage", "DisplayFrom"
end

items = Hotpepper.find(:key=>"guest", :ShopName=>"", :Count=>"10")
items.each do |result|
  puts result.shop_name
  puts result.pc_large_img
end

実行結果

甘太郎 桜橋店
http://www.hotpepper.jp/s/08/J000012508/IMG/p_top_mu.jpg
甘太郎 渋谷桜ヶ丘店
http://www.hotpepper.jp/s/46/J000031346/IMG/p_top_mu.jpg
イタリア厨房 Pisolino<ピソリーノ> 桜田店
http://www.hotpepper.jp/s/99/J000034199/IMG/p_top_mu.jpg
芋蔵 桜木町店
http://www.hotpepper.jp/s/46/J000026346/IMG/p_top_mu.jpg
馬え田(UMAEDA) 桜丘店
http://www.hotpepper.jp/s/89/J000678089/IMG/p_top_mu.jpg
桜花
http://www.hotpepper.jp/s/88/J000052088/IMG/p_top_mu.jpg
お肉とお魚 なごみ空間 炭火彩宴 桜食堂
http://www.hotpepper.jp/s/36/J000032736/IMG/p_top_mu.jpg
おばんざい・手巻き寿司 〜個室乃華 夜桜美人〜
http://www.hotpepper.jp/s/49/J000650449/IMG/p_top_mu.jpg
おぼろ月夜 桜通店
http://www.hotpepper.jp/s/01/J000017001/IMG/p_top_mu.jpg
黄桜 はなしべ
http://www.hotpepper.jp/s/12/J000033512/IMG/p_top_mu.jpg

ホットペッパーでしか試してません!ぐるナビも動くと信じてます。IDとらねばっ
LivedoorWeatherのような単一結果には対応してません。
singleメソッドで頑張ればいけますが・。

今回はホットペッパーの「Shop」をひとつのインスタンスにしたかったのですが、
前回のような方法の方が良いのかなぁ・・?
辛口意見お願いします。