ActionWebService::Struct – Complex Data Structures
December 16, 2007
Filed under Ruby
Tags: aws, data structures, rails, Ruby, struct, web service
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