Last Updated: September 09, 2019
·
24.04K
· avgp

SOAP in Ruby with Savon 2 and nested attributes

So you want to consume SOAP in Ruby? Well, Savon is for you!

The sad thing is: The moment you go beyond very simple SOAP requests, you'll get radio silence from the documentation there.

Even googling around turns up a lot of things that work for Savon version 1, but are slightly different in version 2.

This post is for version 2, even tho version 3 is around the corner
Okay assume a WSDL on an endpoint server running TLSv1 and a self-signed certificate.
It also uses multiple XML namespaces and attributes on sub elements within the request.

Here's an example request:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mag="http://magiccompany.com/supermagic">
   <soapenv:Header/>
   <soapenv:Body>
      <mag:example>
         <someStuff>123</someStuff>
         <someAuthStuff xmlns:n2="http://magiccompany.com/specialstuff"
      xsi:type="n2:SpecialLogin">
           <username>abc</username>
           <password>test123</password>
         </someAuthStuff>
      </mag:example>
   </soapenv:Body>
</soapenv:Envelope>

Now we need a couple of adjustments to the Savon defaults.

Setting attributes

Now in v2 of Savon you can set attributes on the root element of the request (in our case mag:example by adding an attributes key to the request hash like this:

client.call(:someMethod, message: { example: {} }, attributes: {someAttr: 123})
So this base is easily covered.

Nested attributes

The important bit in this XML: The additional namespace and the xsi:type on the nested element. So how do we put the two attributes there in Savon 2?

Well, here comes the super unobvious magical pony ride: You can specify them by putting an :attributes! key with a hash for the elements you want to set attributes for. Fun!

require 'savon'
client = Savon.client(wsdl: 'wsdl/magic.wsdl', ssl_verify_mode: :none, ssl_version: :TLSv1)

client.call(:example, message: {
  someStuff: 123,
  someAuthStuff: {
    username: "abc",
    password: "test123"
  }, 
  :attributes! => {
   someAuthStuff: {
     "xsi:type" => "n2:SpecialLogin",
     "xmlns:n2" => "http://magiccompany.com/specialstuff"
   }
 }
})

Yes, yes - inconsistent use of the old and the new hash syntax, but for that one key I don't wanna switch to the old syntax everywhere else. That's why.

So now have fun, live long and prosper.

6 Responses
Add your response

Hi Martin,

I have a basic doubt on Savon 2.

Is that possible to the XML file as a call request ?

example :

require 'rubygems'
require 'savon'

client = Savon.client(wsdl: 'http://myown/mine.wsdl?wsdl")
x= "SOME XML CONTENT request (which is working perfectly in soup ui)"
a=client.call(x) # this line won't work now. is there any other alternate method will support this ?

Awaiting your reply

over 1 year ago ·

This is the only answer which helps me! Thanks a lot! My SOAP request is working like a charm now!

over 1 year ago ·

from the v2 doc: http://savonrb.com/version2/locals.html

xml

If you need to, you can even shortcut Savon's Builder and send your very own XML.

client.call(:authenticate, xml: "<envelope><body></body></envelope>")

over 1 year ago ·

Thanks for the helpful explanation and examples.
Is it possible to repeat a parameter several times, like in
message: {
param: 1,
param:2,
param:3
}
which does of course not work since message is a hash.

over 1 year ago ·

I have XML having same node differ with id then how to create this request

<multiRef xmlns:ns2="http://iconclude.com/webservices/rss/v2.0/soap" id="id2" soapenc:root="0" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns4:WSFlowInput">
<listValues soapenc:arrayType="xsd:string[2]" xsi:type="soapenc:Array">
<listValues xsi:type="xsd:string">local</listValues>
<listValues xsi:type="xsd:string">remote</listValues>
</listValues>
<name xsi:type="xsd:string">phost</name>
<value xsi:type="xsd:string">phlls17-re0</value>
</multiRef>

<multiRef xmlns:ns3="http://iconclude.com/webservices/rss/v2.0/soap" id="id3" soapenc:root="0" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns3:WSFlowInput">
   <listValues soapenc:arrayType="xsd:string[2]" xsi:type="soapenc:Array">
      <listValues xsi:type="xsd:string">local</listValues>
      <listValues xsi:type="xsd:string">remote</listValues>
   </listValues>
   <name xsi:type="xsd:string">name</name>
   <value xsi:type="xsd:string">show interfaces extensive</value>
</multiRef>

<multiRef xmlns:ns4="http://iconclude.com/webservices/rss/v2.0/soap" id="id4" soapenc:root="0" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns3:WSFlowInput">
   <listValues soapenc:arrayType="xsd:string[2]" xsi:type="soapenc:Array">
      <listValues xsi:type="xsd:string">local</listValues>
      <listValues xsi:type="xsd:string">remote</listValues>
   </listValues>
   <name xsi:type="xsd:string">variables</name>
   <value xsi:type="xsd:string">port_num = ge-0/0/1</value>
</multiRef>

The parameters are name and value

over 1 year ago ·

You can also specify attributes by prefixing the hash key with "@".

client.call(:example, message: {
  someStuff: 123,
  someAuthStuff: {
    "username" => "abc",
    "password" => "test123",
    "@xsi:type" => "n2:SpecialLogin",
    "@xmlns:n2" => "http://magiccompany.com/specialstuff"
  }
 }
})
over 1 year ago ·