Birth and death of an object in Ruby

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.

  • saldan

    Hi Andrea, thanks for you post, it seems very interesting but you have to be aware of how often the Garbage Collector runs in order to free the memory allocated (and you definalizer as well)…
    On Ruby GC it does not run when your variables are out of their scope but when the RUBY_GC_MALLOC_LIMIT is reached (and of course when your application ends working) and unfortunately we can’t know when this will occur. We could low down this value but we can have very big problems on Ruby performance.

    Take a look at this to see how to fine tune the GC:
    http://www.coffeepowered.net/2009/06/13/fine-tuning-your-garbage-collector/

    I would opt for a block version that you can use on send method, in that case you will have a scope and you can close the http connection at the end of the block.

    • http://andreamostosi.name Andrea Mostosi

      Hi Saldan, thank you for your comment. You are right, a block version would be better for general use.

      In this case I’m building a rake task that open some different connection with different parameters. All connections have to be closed at the end (because it’s a logical connection and if you leave it open anyone can access if he guess the code, there is no short timeout). This solution saves you from forgetting. At the end of task each connection is closed.

      Anyway I plan to use a block version for calls made within the application. If you have any helpful advice you are welcome.

      Thanks for the feedback ;)