tags:

views:

115

answers:

2

I'm trying to create a Messages object that inherits Array. The messages will gather a group of Message objects. I'm trying to create an xml output with ROXML that looks like this:

<messages>
    <message>
        <type></type>
        <code></code>
        <body></body>
    </message>
    ...
</messages>

However, I can't figure out how to get the message objects in the Messages object to display in the xml. Here is the code I've been working with:

require 'roxml'

class Message
  include ROXML

  xml_accessor :type
  xml_accessor :code
  xml_accessor :body
end

class Messages < Array
  include ROXML

  # I think this is the problem - but how do I tell ROXML that
  # the messages are in this instance of array?
  xml_accessor :messages, :as => [Message]

  def add(message)
    self << message
  end

end


message = Message.new
message.type = "error"
message.code = "1234"
message.body = "This is a test message."

messages = Messages.new
messages.add message

puts messages.length
puts messages.to_xml

This outputs:

1
<messages/>

So, the message object I added to messages isn't getting displayed. Anyone have any ideas? Or am I going about this the wrong way?

Thanks for any help.

+1  A: 

I don't think what you want is possible. You are somehow trying to get access to the internal state of the Array class, which is not only impossible because on most implementations those internals are hidden away in the C/C++/Java/.NET/Objective-C/ABAP runtime but also quite simply a bad idea and bad object-oriented design.

The thing is, Messages is really not an Array, therefore it shouldn't inherit from Array. Tell me: are you really 100% sure that your Messages class is able to faithfully fulfill the contracts of all 81 methods on Array? And what do assoc, rassoc, rindex and transpose even mean, when applied to Messages?

You'd be much better off using delegation instead of inheritance here. This gives you a nice named entity that you can pass to xml_accessor:

require 'forwardable'
require 'roxml'

class Messages
  extend Forwardable
  include ROXML

  class << self; alias_method :[], :new end

  xml_reader :messages, :as => [Message]

  def initialize(*messages) @messages = messages end

  def_delegators :messages, :length, :<<
end

Note: I also changed a couple of other things here. For example, I personally believe that an object should be valid and usable after it is constructed. In your version of the code, a Message is basically invalid after it is constructed and only becomes valid after you call the type=, code= and body= setters:

class Message
  include ROXML

  class << self; alias_method :[], :new end

  xml_reader :type, :body
  xml_reader :code, :as => Integer

  def initialize(type=nil, code=nil, body=nil)
    @type, @code, @body = case opts = type
    when Hash
      opts[:type], opts[:code], opts[:body]
    else
      type, code, body
    end
  end
end

Here's a slightly expanded usage example:

msgs = Messages[Message['error', 1234, 'This is a test message.'], Message[]]

msgs << Message[
  type: 'warning', 
  code: 4815162342, 
  body: 'This is another test message.'
]

puts msgs.to_xml
# => <messages>
# =>   <message>
# =>     <type>error</type>
# =>     <body>This is a test message.</body>
# =>     <code>1234</code>
# =>   </message>
# =>   <message/>
# =>   <message>
# =>     <type>warning</type>
# =>     <body>This is another test message.</body>
# =>     <code>4815162342</code>
# =>   </message>
# => </messages>
Jörg W Mittag
Very good explanation. I now agree that messages shouldn't be inheriting from Array... I guess I thought it should because messages would be a collection. Thanks for the help.
Brian D.
A: 

I'm encountering a similar issue, and am hoping you might help. The difference is that my collection class doesn't inherit from Array. Regardless, if I have an array of ROXML classes ('Message' in the above example, [Message, Message, Message]), calling to_xml on that array yields an empty doc:

 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<messages type=\"array\">\n</messages>\n"

Why the empty array?

findchris