Friday, November 20, 2009

Accessing soap web services in Ruby using soap4r.

Recently I had to access a soap web service written in .NET with a Rails application, the most "common" way is using the soap4r gem. There are another alternatives, one of them is "handsoap" but i decided to go with soap4r, because it looked simpler and the project is more mature. The bad thing is that is difficult to find examples, that's why I am writing this article to help someone who does not have any prior experience with this library.

Standard ruby installation comes with soap4r but it's better to use the latest version, so install the gem.

sudo gem install soap4r

Then you should know the url of the wsdl of the service you want to consume, we are going to use this which is freely available:

http://www.webservicex.net/CurrencyConvertor.asmx?WSDL

once you have the url, there are 2 options:
1. create proxy classes with a utility called wsdl2ruby which resides on the bin directory of your library installation.
2. create the driver and call the methods directly passing a hash in the form of an XML as parameter.

The first one didn't work for me because the classes were not representing exactly the wsdl. There are possibly some wsdl directives which the utility doesn't recognizes, that depends on the service. So my only choice was the second one which ended to be more intuitive.

Hashes and XML
It's very easy to transform xml into hashes and viceversa, in Rails if you have a hash instance you can simply call my_hash.to_xml and it returns the xml string with the keys as nodes and the data associated inside.

these are a couple of examples

For example, suppose you have this hash:
 
{
"band" => {
"guitars" => [{"name" => "Allan Holdsworth"},{"name" => "Frank Gambale"}],
"drums" => "Vinnie Colaiuta",
"bass" => "Gary Willis"
}
}


it will turn into this XML:


<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<hash>
<band>
<bass>Gary Willis</bass>
<drums>Vinnie Colaiuta</drums>
<guitars type=\\\"array\\\">
<guitar>
<name>Allan Holdsworth</name>
</guitar>
<guitar>
<name>Frank Gambale</name>
</guitar>
</guitars>
</band>
</hash>


The idea is very simple, xml tags can be nested defining hashes of hashes, and a tag which expects several same tags on the inside has to be defined with an array.
You can go the other way with Hash.from_xml and passing the xml

So Hash.from_xml(band.to_xml) should return the original hash.



Now lets checkout the web service definition

you can ckeckout the web service methods in:
http://www.webservicex.net/CurrencyConvertor.asmx

the web service definition is in:
http://www.webservicex.net/CurrencyConvertor.asmx?WSDL

you can go to the only method available in this service on:
http://www.webservicex.net/CurrencyConvertor.asmx?op=ConversionRate
go to the section which describes the soap protocol and there is the exchanging xml messages format look in the soap protocol part, this is where it describes the method parameters:



...
<ConversionRate xmlns=\"http://www.webserviceX.NET/\">
<FromCurrency>AFA or ALL or DZD ....</FromCurrency>
<ToCurrency>AFA or ALL or DZD ...</ToCurrency>
</ConversionRate>
...



So the parameters will be the hash:

{"FromCurrency" => "...", "ToCurrency" => "..."}


and the response will be in:



...
<ConversionRateResponse xmlns=\"http://www.webserviceX.NET/\">
<ConversionRateResult>double</ConversionRateResult>
</ConversionRateResponse>
...



in this case the response will be only a "double"


Now the code:

In rails add this to your environment.rb to include the latest library version:

config.gem 'soap4r', :lib => 'soap/wsdlDriver', :version => '1.5.8'

you should change the version number to the latest you have install


If you are using just Ruby:


require "rubygems"
gem 'soap4r'
require "soap/wsdlDriver"


and this is the Ruby code:




require "rubygems"
gem "soap4r"
require "soap/wsdlDriver"
wsdl = "http://www.webservicex.net/CurrencyConvertor.asmx?WSDL"

driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver

#driver.wiredump_dev = STDOUT

params = {
"FromCurrency" => "USD",
"ToCurrency" => "UYU"
}

puts driver.conversionRate(params).conversionRateResult




with driver.wiredump_dev = STDOUT all the exchanging messages are going to STDOUT. Uncomment this if you want to know the exchanging xml messages.
The response is everything inside the "<conversionrateresult>" tag, in this case is a simple value which can be parsed easily. Normally it's an xml that can be parsed with any Ruby xml library.

So, the steps are:
-create the driver from the wsdl
-build a hash based on the web service method parameters, which can be taken from the asmx method description
-call the driver method with the hash as parameter, and call the xxxResult method, to get the string response

hope it helps somebody