XML serialization in the context of generating other Builder XML

Hey all,

I've been spending some time trying to figure out the best way to
create some XML output while mixing in ActiveRecord serialization.
After looking through the source for the XmlMarkup, XmlBase, and
ActiveRecord::XmlSerializer (in Rails 2. I also looked at Rails 3 and
the code seems to be largely in ActiveModel) classes, the best I could
find was using the "tag!" method to output the attributes/methods of
an ActiveRecord object individually. Since I wanted to have all of
the goodness of just specifying an array of attributes/methods/procs I
ended up coding my own monkey patch (see below), but I'd love it if
this sort of functionality was part of Rails so that my monkey patch
doesn't break and so that anybody could use it. If there's no better
solution currently implemented, I'd be happy to spend some time
working on the project. I just thought I'd check in with ya'll
first :wink:

So the behavior I want would look something like this (as an sample
post.xml.builder view):

xml.instruct!
xml.active_record_model_tag!(@post, :attributes =>
[:id, :body, :updated_at]) do
  xml.active_record_model_tag!(@post.author)
  @post.comments.each do |comment|
    xml.active_record_model_tag!(comment, :attributes =>
[:id, :body, :created_at], :methods => [:up_vote_percentage])
  end
end

I also like this solution because, while I've been using "to_xml" for
a long time and while I really like it, I think it starts to get
unreasonably complex when I'm using the :only key in the options to
display attributes in subsequent associations of the main object (the
author and comments associations off of the @post main object in the
example above) and you may get name conflicts where maybe you wanted
the "created_at" for just the comments and not the post.

I've written the (incomplete but sufficient for my needs) monkey patch
below to address the issue, but again, I'd love for this to be built
into Rails and for it to use the ActiveModel serialization. If it
needs to be implemented, my main question is: should this be continue
to be a monkey patch of Builder which is defined by Rails, or would it
be best to find some way to implement it via Builder and utilize in
Rails?

module Builder
  class XmlMarkup < XmlBase
    def active_record_model_tag!(model_object, options = {})
      undefined_keys = options.keys.collect(&:to_sym) -
[:attributes, :methods]
      raise ArgumentError, "Undefined keys:
#{undefined_keys.to_sentence}" unless undefined_keys.empty?

      model_class = model_object.class

      attributes = options[:attributes] || model_class.column_names
      methods = options[:methods] || []

      self.tag!(model_class.to_s.tableize.singularize) do
        attributes.each do |attribute|
          value = model_object.send(attribute)
          type = model_class.columns_hash[attribute.to_s].type

          properties = {}
          properties = properties.merge(:type => type) if type
          properties = properties.merge(:nil => true) if value.nil?
          self.tag!(attribute, value, properties)
        end

        methods.each do |method|
          value = model_object.send(method)

          properties = properties.merge(:nil => true) if value.nil?
          self.tag!(method, value, properties)
        end

        yield if block_given?
      end
    end
  end
end

So the behavior I want would look something like this (as an sample
post.xml.builder view):

xml.instruct!
xml.active_record_model_tag!(@post, :attributes =>
[:id, :body, :updated_at]) do
xml.active_record_model_tag!(@post.author)
@post.comments.each do |comment|
xml.active_record_model_tag!(comment, :attributes =>
[:id, :body, :created_at], :methods => [:up_vote_percentage])
end
end

You can actually get something pretty close to this already, it's just
'backwards' from how you're thinking about it:

xml.instruct!
@post.to_xml(:builder=>xml, :skip_instruct=>true)
@post.comments.each do |comment|
  comment.to_xml(:builder=>xml, :skip_instruct=>true)
end

Ah, excellent, thank you! I kind of like the syntax of always calling
methods on the XML builder object, but this works well. I figured
that if Rails supported this it probably be passing in the builder
somehow.

I looked at the Rails 3 documentation (beta3 from apidock:
http://apidock.com/rails/v3.0.0.beta3/ActiveRecord/Serialization/to_xml
) and this option didn't seem to be listed. Should I submit a doc
patch?

Thanks again,
Brian
;p

I looked at the Rails 3 documentation (beta3 from apidock:
http://apidock.com/rails/v3.0.0.beta3/ActiveRecord/Serialization/to_xml
) and this option didn't seem to be listed. Should I submit a doc
patch?

Yeah, this would probably be a good example to include. we could also
consider changing to_xml to default :skip_instruct to true if :builder
is provided.