ActiveResourceが使うXMLパーサの切り替え

やりたいこと

 ActiveResourceが使うXMLパーサを切り替えたい。JSONを使えれば、そっちの方が高速に処理できそうですが、メッセージフォーマットがXMLにしか対応していないウェブサービスもありますよね。
 検索してみると、関係しそうなところで、次のようなサイトが見つかりました。

ソースのトレース

 自分でも、コードを追って確認してみたいと思います。railsのバージョンは2.3.2。まずは、findから。

ActiveResource::Base.find

C:\ruby\lib\ruby\gems\1.8\gems\activeresource-2.3.2\lib\active_resource\base.rb

      def find(*arguments)
        scope   = arguments.slice!(0)
        options = arguments.slice!(0) || {}

        case scope
          when :all   then find_every(options)
          when :first then find_every(options).first
          when :last  then find_every(options).last
          when :one   then find_one(options)
          else             find_single(scope, options)
        end
      end

 ActiveResource::Base.findの第1引数を:oneにしているので、find_oneが呼び出されます。

ActiveResource::Base.find_one

C:\ruby\lib\ruby\gems\1.8\gems\activeresource-2.3.2\lib\active_resource\base.rb

        # Find a single resource from a one-off URL
        def find_one(options)
          case from = options[:from]
          when Symbol
            instantiate_record(get(from, options[:params]))
          when String
            path = "#{from}#{query_string(options[:params])}"
            instantiate_record(connection.get(path, headers))
          end
        end

 :fromとして文字列を指定しているので、when String以下が実行されます。connection.getでHTTP GETをしているようです。

ActiveResource::Connection.get

C:\ruby\lib\ruby\gems\1.8\gems\activeresource-2.3.2\lib\active_resource\connection.rb

    # Execute a GET request.
    # Used to get (find) resources.
    def get(path, headers = {})
      format.decode(request(:get, path, build_request_headers(headers, :get)).body)
    end

 format.decodeでデコードしているようですが、まずは、formatが何ものかを確認します。base.rbファイル内で定義されています。

ActiveResource::Base.format

C:\ruby\lib\ruby\gems\1.8\gems\activeresource-2.3.2\lib\active_resource\base.rb

      # Returns the current format, default is ActiveResource::Formats::XmlFormat.
      def format
        read_inheritable_attribute(:format) || ActiveResource::Formats[:xml]
      end

 :formatが設定されていないと、デフォルトでXMLとなります。ActiveResource::Formats[]を見ます。

ActiveResource::Formats[]

C:\ruby\lib\ruby\gems\1.8\gems\activeresource-2.3.2\lib\active_resource\formats.rb

    # Lookup the format class from a mime type reference symbol. Example:
    #
    #   ActiveResource::Formats[:xml]  # => ActiveResource::Formats::XmlFormat
    #   ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
    def self.[](mime_type_reference)
      ActiveResource::Formats.const_get(mime_type_reference.to_s.camelize + "Format")
    end

 なので、先ほどのformat.decodeのformatは、ActiveResource::Formats::XmlFormatインスタンスとなります。format.decodeを見ます。

ActiveResource::Formats::XmlFormat.decode
      def decode(xml)
        from_xml_data(Hash.from_xml(xml))
      end

 Hash.from_xmlXMLからHashへマッピングしているようです。

Hash.from_xml

C:\ruby\lib\ruby\gems\1.8\gems\activesupport-2.3.2\lib\active_support\core_ext\hash\conversions.rb

          def from_xml(xml)
            typecast_xml_value(unrename_keys(XmlMini.parse(xml)))
          end

XmlMini.parse(xml)でパースしています。

XmlMini.parse

(ただし、パーサとしてREXMLが指定されている場合です。)
C:\ruby\lib\ruby\gems\1.8\gems\activesupport-2.3.2\lib\active_support\xml_mini\rexml.rb

    def parse(string)
      require 'rexml/document' unless defined?(REXML::Document)
      doc = REXML::Document.new(string)
      merge_element!({}, doc.root)
    end

rexml.rbの1つ上のフォルダにあるxml_mini.rbを見てみます。

XmlMini

C:\ruby\lib\ruby\gems\1.8\gems\activesupport-2.3.2\lib\active_support\xml_mini.rb

module ActiveSupport
  # = XmlMini
  #
  # To use the much faster libxml parser:
  #   gem 'libxml-ruby', '=0.9.7'
  #   XmlMini.backend = 'LibXML'
  module XmlMini
    extend self

    attr_reader :backend
    delegate :parse, :to => :backend

    def backend=(name)
      if name.is_a?(Module)
        @backend = name
      else
        require "active_support/xml_mini/#{name.to_s.downcase}.rb"
        @backend = ActiveSupport.const_get("XmlMini_#{name}")
      end
    end

    def with_backend(name)
      old_backend, self.backend = backend, name
      yield
    ensure
      self.backend = old_backend
    end
  end

  XmlMini.backend = 'REXML'
end

 コメントアウトされている説明を読むと、より高速なlibxmlを使う場合には、バージョン0.9.7の'libxml-ruby'gemをインストールし、XmlMini.backend = 'LibXML'とせよ、とのことです。デフォルトは、XmlMini.backend = 'REXML'ですね。

ということで

 指示通りに試してみようと思います。