ActionWebService::Struct – Complex Data Structures

Posted On December 16, 2007

Filed under Ruby
Tags: , , , , ,

Comments Dropped leave a response

Problem

Sometimes it is very useful to have something a little bit more complex inside a ActionWebService::Struct than the base types, arrays or other ActionWebService::Struct types. But this is hard to accomplish as the complex data structures, are not transportable by the web service layer (e.g. SOAP libraries cannot map them to a soap definition).

Discussion

Usually one wishes to use more complex data structures because they provide for more efficiency or convenience, however the data itself usually can be represented in terms of the allowed ActionWebService::Struct types.

From this several solutions might be devised:

  • Use just the allowed simple types and forget suffer the loss of efficiency – sometimes it is a lot easier to go this way and the loss is not great.
  • Have 2 classes, one for sending the data, one for manipulating it and transfer the data between them explicitly – not the cleanest solution in the world, but at the same time it means that all business logic uses the custom class, which has the data structures and is not tied to the ActionWebService in any way (allows for easy portability).
  • Mess around with the ruby variable accessing techniques and use the 2nd approach with only one class – not the most pretty solution, but it removes the need to explicitly convert data from one format to the other.

Solution

First 2 solutions are easy to implement, the third one is a bit more tricky.

The key lies in the interface of the ruby Object class.

Basically, one needs to declare 2 structures in one.

Consider a simple and a slightly artificial example of handing an class Things, where you would like to have a SortedSet containing strings, but all you can have is a list of strings.

class Things << ActionWebService::Struct

  # Declare the AWS compatible definitions
  member :list, [:string]

  def initialize
    init
  end

  def init
    @set = SortedSet.new
  end

  # Redefine the accessor
  def list
    @set.to_a
  end

  # Redefine the mutator
  def list=(other)
    # When the web service (e.g. SOAP) string is deserialised
    # the constructor is not called,
    # so we have to initialize the state explicitly
    init

    @set.merge(other)
  end

  # This method is called, when instance variables are accessed
  def instance_variable_get(msg)
    # Intercept the list request
    return list if msg == '@list'
    super msg
  end

  def instance_variables
    # Unless we remove the set variable from the list of instance variables,
    # the library will still try to map into a its request/response string
    list = super
    list.delete_if do |name|
      name == '@set'
    end
    list
  end

end

Respond now.