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).
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.
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 [/sourcecode]