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.
Written by Martin Naumann
Related protips
6 Responses
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
This is the only answer which helps me! Thanks a lot! My SOAP request is working like a charm now!
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>")
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.
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
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"
}
}
})