views:

1034

answers:

3

Hi

I am learning Ruby and I have written the following code to find out how to consume SOAP services:

require 'soap/wsdlDriver'
wsdl="http://www.abundanttech.com/webservices/deadoralive/deadoralive.wsdl"
service=SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
weather=service.getTodaysBirthdays('1/26/2010')

The response that I get back is:

#<SOAP::Mapping::Object:0x80ac3714 
{http://www.abundanttech.com/webservices/deadoralive} getTodaysBirthdaysResult=#<SOAP::Mapping::Object:0x80ac34a8 
{http://www.w3.org/2001/XMLSchema}schema=#&lt;SOAP::Mapping::Object:0x80ac3214 
{http://www.w3.org/2001/XMLSchema}element=#&lt;SOAP::Mapping::Object:0x80ac2f6c 
{http://www.w3.org/2001/XMLSchema}complexType=#&lt;SOAP::Mapping::Object:0x80ac2cc4 
{http://www.w3.org/2001/XMLSchema}choice=#&lt;SOAP::Mapping::Object:0x80ac2a1c 
{http://www.w3.org/2001/XMLSchema}element=#&lt;SOAP::Mapping::Object:0x80ac2774 
{http://www.w3.org/2001/XMLSchema}complexType=#&lt;SOAP::Mapping::Object:0x80ac24cc 
{http://www.w3.org/2001/XMLSchema}sequence=#&lt;SOAP::Mapping::Object:0x80ac2224 
{http://www.w3.org/2001/XMLSchema}element=[#&lt;SOAP::Mapping::Object:0x80ac1f7c&gt;, 
#<SOAP::Mapping::Object:0x80ac13ec>, 
#<SOAP::Mapping::Object:0x80ac0a28>, 
#<SOAP::Mapping::Object:0x80ac0078>, 
#<SOAP::Mapping::Object:0x80abf6c8>, 
#<SOAP::Mapping::Object:0x80abed18>]
>>>>>>> {urn:schemas-microsoft-com:xml-diffgram-v1}diffgram=#<SOAP::Mapping::Object:0x80abe6c4 
{}NewDataSet=#<SOAP::Mapping::Object:0x80ac1220 
{}Table=[#<SOAP::Mapping::Object:0x80ac75e4 
{}FullName="Cully,  Zara" 
{}BirthDate="01/26/1892" 
{}DeathDate="02/28/1979" 
{}Age="(87)" 
{}KnownFor="The Jeffersons" 
{}DeadOrAlive="Dead">, 
#<SOAP::Mapping::Object:0x80b778f4 
{}FullName="Feiffer, Jules" 
{}BirthDate="01/26/1929" 
{}DeathDate=#<SOAP::Mapping::Object:0x80c7eaf4> 
{}Age="81" 
{}KnownFor="Cartoonists" 
{}DeadOrAlive="Alive">]>>>>

I am having a great deal of difficulty figuring out how to parse and show the returned information in a nice table, or even just how to loop through the records and have access to each element (ie. FullName,Age,etc). I went through the whole "getTodaysBirthdaysResult.methods - Object.new.methods" and kept working down to try and work out how to access the elements, but then I get to the array and I got lost.

Any help that can be offered would be appreciated.

A: 

SOAP4R always returns a SOAP::Mapping::Object which is sometimes a bit difficult to work with unless you are just getting the hash values that you can access using hash notation like so

weather['fullName']

However, it does not work when you have an array of hashes. A work around is to get the result in xml format instead of SOAP::Mapping::Object. To do that I will modify your code as

 require 'soap/wsdlDriver'
 wsdl="http://www.abundanttech.com/webservices/deadoralive/deadoralive.wsdl"
 service=SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
 service.return_response_as_xml = true
 weather=service.getTodaysBirthdays('1/26/2010')

Now the above would give you an xml response which you can parse using nokogiri or REXML. Here is the example using REXML

  require 'rexml/document'
  rexml = REXML::Document.new(weather)
  birthdays = nil
  rexml.each_recursive {|element| birthdays = element if element.name == 'getTodaysBirthdaysResult'}
  birthdays.each_recursive{|element| puts "#{element.name} = #{element.text}" if element.text}

This will print out all elements that have any text.

So once you have created an xml document you can pretty much do anything depending upon the methods the library you choose has ie. REXML or Nokogiri

nas
+2  A: 

If you're going to parse the XML anyway, you might as well skip SOAP4r and go with Handsoap. Disclaimer: I'm one of the authors of Handsoap.

An example implementation:

# wsdl: http://www.abundanttech.com/webservices/deadoralive/deadoralive.wsdl
DEADORALIVE_SERVICE_ENDPOINT = {
  :uri => 'http://www.abundanttech.com/WebServices/DeadOrAlive/DeadOrAlive.asmx',
  :version => 1
}

class DeadoraliveService < Handsoap::Service
  endpoint DEADORALIVE_SERVICE_ENDPOINT
  def on_create_document(doc)
    # register namespaces for the request
    doc.alias 'tns', 'http://www.abundanttech.com/webservices/deadoralive'
  end

  def on_response_document(doc)
    # register namespaces for the response
    doc.add_namespace 'ns', 'http://www.abundanttech.com/webservices/deadoralive'
  end

  # public methods

  def get_todays_birthdays
    soap_action = 'http://www.abundanttech.com/webservices/deadoralive/getTodaysBirthdays'
    response = invoke('tns:getTodaysBirthdays', soap_action)
    (response/"//NewDataSet/Table").map do |table|
      {
        :full_name => (table/"FullName").to_s,
        :birth_date => Date.strptime((table/"BirthDate").to_s, "%m/%d/%Y"),
        :death_date => Date.strptime((table/"DeathDate").to_s, "%m/%d/%Y"),
        :age => (table/"Age").to_s.gsub(/^\(([\d]+)\)$/, '\1').to_i,
        :known_for => (table/"KnownFor").to_s,
        :alive? => (table/"DeadOrAlive").to_s == "Alive"
      }
    end
  end
end

Usage:

DeadoraliveService.get_todays_birthdays
troelskn
I had brief look at Handsoap yesterday, is that just a soap client or can I create a Soap server as well just like Soap4r StandaloneServer
nas
On another note can you point me to something that has a comparison between Soap4R and HandSoap. I would be very much interested in looking at the advantages and disadvantages of HandSoap and Soap4R over each other. Obviously neither can have only advantages or disadvantages over another.
nas
It's just a client. There is some justification for the library on the project page, but short story: Handsoap takes more effort up front, but makes it easier to debug code if/when things fail (Which it does because SOAP is a nightmare of a protocol). It also makes for leaner/faster code.
troelskn
A: 

Well, Here's my suggestion.

The issue is, you have to snag the right part of the result, one that is something you can actually iterator over. Unfortunately, all the inspecting in the world won't help you because it's a huge blob of unreadable text.

What I do is this:

File.open('myresult.yaml', 'w') {|f| f.write(result.to_yaml) }

This will be a much more human readable format. What you are probably looking for is something like this:

    --- !ruby/object:SOAP::Mapping::Object 


    __xmlattr: {}

      __xmlele: 

  - - &id024 !ruby/object:XSD::QName 
      name: ListAddressBooksResult <-- Hash name, so it's resul["ListAddressBooksResult"]
      namespace: http://apiconnector.com
      source: 
    - !ruby/object:SOAP::Mapping::Object 
      __xmlattr: {}

      __xmlele: 
      - - &id023 !ruby/object:XSD::QName 
          name: APIAddressBook <-- this bastard is enumerable :) YAY! so it's result["ListAddressBooksResult"]["APIAddressBook"].each
          namespace: http://apiconnector.com
          source: 
        - - !ruby/object:SOAP::Mapping::Object

The above is a result from DotMailer's API, which I spent the last hour trying to figure out how to enumerate over the results. The above is the technique I used to figure out what the heck is going on. I think it beats using REXML etc this way, I could do something like this:

result['ListAddressBooksResult']['APIAddressBook'].each {|book| puts book["Name"]}

Well, I hope this helps anyone else who is looking.

/jason