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.