Sunday, December 13, 2009

Storing single values in Rails with ActiveRecord

Sometimes you want to store a bunch of single values that are not related to anything, and of course they are not constant values, it is better to put it in a constant or in a yaml config file if they are constant values.

This is what i usually do, maybe there is a better solution out there, but this one is very simple.
It's kind of make an active record object to work like a Hash, or key-value store.

Create the migration(name it the way you like it):



class CreateSingleValues < ActiveRecord::Migration
def self.up
create_table :single_values do |t|
t.string :key
t.string :value
end
end

def self.down
drop_table :single_values
end
end



And this is the code:



class SingleValue < ActiveRecord::Base

def self.[](key)
val = self.find_by_key(key.to_s)
val.value if val
end

def self.[]=(key, value)
val = self.find_by_key(key.to_s)
if val
value.nil? ? val.delete : val.update_attribute(:value, value)
else
self.create(:key => key.to_s, :value => value) unless value.nil?
end
end

end




I add the [] operator methods to the active record class.

this way I can store values like this:
SingleValue[:foo] = "bar" or SingleValue["foo"] = "bar"

and then retrieve it with:
SingleValue[:foo] or SingleValue["foo"]

It returns nil if the key is not found, and delete the record if you assign a nil value.
For example SingleValue[:foo] = nil will delete SingleValue[:foo] if it exists.
Also you can do substitution on the key like SingleValue["foo_#{bar}"] if you are going to get the key at runtime.

You may find convenient to add an index on the key column for quicker searches if you are storing many values.

Note that you can only store single string values(or numbers that will be returned as strings).
If you want to store complete objects you can serialize them into the value column with ActiveRecord "serialize" method,
you may have to change the column type to 'text' , the implementation will be the same I think, try it!

Bye!

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