Search code examples
ruby-on-rails-3savon

Issue with a collection of response elements using Savon


I am attempting to use Savon to communicate with a legacy web service using SOAP. I am having an issue with regards to how Savon constructs a hash out of the response envelope.

client = Savon.client("http://my-service-endpoint/?wsdl")

response = client.request :my_soap_operation do
  soap.body do |xml|
    xml.in("test")
  end
end

response.to_hash[:my_soap_operation_response][:out].each do |a|
   puts "Return element: #{a}"
   puts "Name #{a[:name]}"
   puts "Type #{a[:type]}"
   puts "Code #{a[:code]}"
end

end

If the SOAP response contains more than 1 'out' element, such as this:

<mySoapOperationResponse>
  <out>
   <code>C1947944</code>
   <name>Use</name>
   <type>type 1</type>
 </out>
 <out>
   <code>C1947946</code>
   <name>name 2</name>
   <type>type 2</type>
 </out>
 <out>
   <code>C1947947</code>
   <name>name 2</name>
   <type>type 3</type>
 </out>
</mySoapOperationResponse>

The output is as expected:

Return element: {:code=>"C1947944", :name=>"Use", :type=>"type 1"}
Name Use
Type type 1
Code C1947944
Return element: {:code=>"C1947946", :name=>"name 2", :type=>"type 2"}
Name name 2
Type type 2
Code C1947946
Return element: {:code=>"C1947947", :name=>"name 2", :type=>"type 3"}
Name name 2
Type type 3
Code C1947947

However, if the SOAP response only contains a single 'out' element, such as this:

<mySoapOperationResponse>
     <out>
        <code>C1947944</code>
        <name>name 1</name>
        <type>type 1</type>
     </out>
</mySoapOperationResponse>

I see this in the console

Return element: [:code, "C1947944"]

... and then I get the error message

can't convert Symbol into Integer

It would appear as though the :name and :type elements are not present in the SOAP response, however I can see them via the response XML message.

I am using Savon v1.2.0.

Addendum

I added the following code to try to further debug the issue:

 response = client.request :my_soap_operation do
  soap.body do |xml|
    xml.in("test")
  end
 end

pp response.to_hash

response.to_hash[:my_soap_operation_response][:out].each do |a|
   pp a
   puts "Return element: #{a}"
   puts "Name #{a[:name]}"
   puts "Type #{a[:type]}"
   puts "Code #{a[:code]}"

And I can see the elements being pretty-printed in the response hash. Here is a snapshot of the console:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
    <mySoapOperationResponse>
        <out>
            <code>C1947944</code>
            <name>name 1</name>
            <type>type 1</type>
        </out>
    </mySoapOperationResponse>
 </soapenv:Body>
</soapenv:Envelope>

{:my_soap_operation_response=>
    {:out=>{:code=>"C1947944", :name=>"name 1", :type=>"type 1"},
        :@xmlns=>"http://namespace.removed.com/"}}

[:code, "C1947944"]
Return element: [:code, "C1947944"]

However it seems like inside the .each loop (looping over each 'out' element), only the first element can be accessed?


Solution

  • I think the issue is, that Savon doesn't return an Array in the second case. You need to check whether [:out] is an Array or a single value and act accordingly. What I did was I wrapped the single value into an Array of one element to keep my program structure simple.

    def wrapped_soap_call
      # prepare Savon::Client to your liking
      client = Savon::Client.new do
        # your stuff here
      end
    
      # call the SOAP WS
      result = client.request :wsdl, :function do
        # your stuff here
      end
    
      list = result.to_hash[:response][:out]
      # if there was only one item list is not an array!
      if list.is_a? Array
        return list
      else
        return Array.new << list
      end
    end
    

    That works for me.