Last week I played with a SOAP API with savon.rb
to develop a newsletter system.
This API require authentication with a two-way handshake. At first you send a login, a password and a key and it gave you back a token. When you ended operation you should send a “close connection” command.
This is a popular way but when you use it in a model seems a bit tricky so I decided to hide this method inside a custom class.
First I create ApiWrapper
class that wrap SOAP operation:
class ApiWrapper def initialize @client = Savon::Client.new APP_CONFIG[:wsdl] end def open_connection response = do_soap_request("openApiConnection", { :login => APP_CONFIG[:login], :pwd => APP_CONFIG[:pwd], :key => APP_CONFIG[:key] }) @token = response[:token] end def close_connection response = do_soap_request("closeApiConnection", { :token => @token }) end def do_soap_request(method, arguments) response = @client.request(:api, method) do soap.body = arguments end response.to_hash end end
Then use the ApiWrapper
object inside the model:
class Newsletter def initialize @api = ApiWrapper.new end def send # do something end end
Now we can use the @api
object for communicate with the API. I want to hide the authentication process, first attempt can be to incapsulate operation inside each method:
def send @api.open_connection @api.do_soap_request("send", arguments) @api.close_connection end
It works but the webservice il very slow, opening and closing connection require about 20 seconds and for hundred operation is too much time.
Another way is to encapsulate authentication process inside object initialization and finalization. For this we need something similar to a C++ constructor and destructor method. Ruby has got the initialize
method similar to constructor but it does not have a standard destructor. Luckily we can emulate it.
The ObjectSpace
module enable you to define block that is executed when the object is destroyed using the define_finalizer
method.
ObjectSpace.define_finalizer(self, Proc.new{@api.close_connection})
In the initialize
method we can open the connection and define this finalizer. We can rewite the method as follow:
def initialize @api = ApiWrapper.new @api.open_connection ObjectSpace.define_finalizer(self, Proc.new{@api.close_connection}) end
Now we can use the object with easier and more efficient connection management.
> n = Newsletter.new # implicit connection open > n.send # connection is still open > n.send # connection is still open > n.send # connection is still open > quit # implicit connection close
These methods are very useful (also) to mask unnecessary complexity and make the data access more readable.