libXML for Active Resource 2.0

Posted by Bart ten Brinke Mon, 25 Feb 2008 08:19:39 GMT

I received an email from Stevie Clifton today, asking about our libXML patch for rails 2.0. As we have been running 2.0 for quite some time now, I never realised I forgot to post the new overrides.

The file below goes into /config/initializers/libxml.rb

# This is actally a fix for activeresource as it
# will behave incorrectly when it encounters
# Complex xml files. This override fixes this,
# but it should be submitted to rails trunk.
module ActiveResource
  module Formats
    module XmlFormat
      private
      def from_xml_data(data)
          if data.is_a?(Hash) && data.keys.size == 1
            from_xml_data(data.values.first)
          else
            data
          end
        end      
    end
  end
end

module Nedap #:nodoc:
  module Hash #:nodoc:
    module Conversions

      def self.included(klass)
        require 'xml/libxml'
        klass.extend(ClassMethods)
      end

      module ClassMethods

        # Hash from_xml mixin that uses libxml.
        # This ensures a 20x speed increase
        # Compared to libxml. Plus it is less ugly.
        def from_xml(xml) 
          result = XML::Parser.string(xml).parse 
          return { result.root.name.to_s => xml_node_to_hash(result.root)} 
        end 

        def xml_node_to_hash(node) 
          # If we are at the root of the document, start the hash 
          if node.element? 
           if node.children? 
              result_hash = {} 

              node.each_child do |child| 
                result = xml_node_to_hash(child) 

              if child.name == "text"
                if !child.next? and !child.prev?
                  return result
                end
              elsif result_hash[child.name] 
                  if result_hash[child.name].is_a?(Object::Array) 
                    result_hash[child.name] << result 
                  else 
                    result_hash[child.name] = [result_hash[child.name]] << result 
                  end 
                else 
                  result_hash[child.name] = result 
                end              
              end 

              return result_hash 
            else 
              return nil 
           end 
           else 
            return node.content.to_s 
          end 
        end          

      end        
    end
  end
end 

Hash.send :include, Nedap::Hash::Conversions

There you go. You now have a blazingly fast active resource! If you want some more bang out of your resource, add the following mixins to the initializer too:

# Add inflate to NET class (zLib support)
module Net
  class HTTPResponse
     def inflate!
       require 'zlib'
       @body = Zlib::Inflate.inflate(@body)
     end
   end
 end

# Increase timeout and buffersize for big XML files
module Net
  class BufferedIO 
    def rbuf_fill
      timeout(3000) { 
        @rbuf << @io.sysread(32768) 
      }
    end 
  end
end

Posted in  | Tags , , ,  | no comments

Testing a REST full activeresource

Posted by Bart ten Brinke Wed, 04 Jul 2007 13:49:28 GMT

Active Resources are nice, nut when you're trying to test your active resources , you're in for a long night. Luckally someone did a lot of the basic work for us. The HTTPMock class in ActiveResource is a very handy tool for testing your active resources. Unfortunately there is absolutely no documentation about how to use it, and it has some behaviour you might not expect. Therefore I present you with this code example.

The remote models of our REST connection live in Connections::IO::REST. When requesting the Employees via Connections::IO::REST::Employee.find(:all), we are actually requesting /employees.xml from our HTTPMock class. The HTTPMock class then just outputs the XML file of the testset we got from our friend which wrote the REST-connector we are connecting to. In the example, this is: /test/remote_fixtures/connector/employees.xml

 class IORestConnectionTest < Test::Unit::TestCase

 def setup
   @pre      = Connections::IO::REST
   @mock_url = 'http://localhost/'
   @headers  = {"User-Agent"=>"Moves", "Accept-Encoding"=>"deflate", "Content-Type"=>"application/xml"}
   ActiveResource::HttpMock.respond_to do |mock|
     mock.get "/employees.xml", @headers, ioconnect_resources('employees')
     mock.get "/clients.xml"  , @headers, ioconnect_resources('clients')
   end

   # Overwrite all site URLS
   @pre::Resource.site = @mock_url
 end

 def ioconnect_resources(name)
   path = File.join(RAILS_ROOT, "test", "remote_fixtures", "ioconnect", "#{name.to_s}.xml")
   return nil unless File.exists?(path)
   File.read path
 end

 def teardown
     ActiveResource::HttpMock.reset!
 end

 def test_should_find_all_employees_via_rest
   employees = @pre:Employee.find(:all)
   assert_equal employees.count, 20
 end

 end

One last note: HTTPMock was created to be as simple as possible. If you request something with a different header or parameters in the URL, it will return absolutely nothing. So if you get no response from your mock activeresource, be sure to check the headers you sent.

Posted in  | Tags , , , ,  | 1 comment