Ruby ROXML - how to get an array to render its xml?
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.
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>
精彩评论