What's New in Edge Rails: A More Flexible to_xml

Posted by ryan
at 10:26 AM on Friday, April 13, 2007



The to_xml method on your ActiveRecord classes has a bunch ‘o options that let you refine how you want your models to be serialized to xml. You’ve got your :only and :except options which let you filter in/out certain attributes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
user = User.find(1)

user.to_xml(:except => [:id, :created_at])
#=>
# <user>
#   <name>Ryan</name>
#   <email>ryan@spamme.com</email>
# </user>

user.to_xml(:only => [:email])
#=>
# <user>
#   <email>ryan@spamme.com</email>
# </user>

(Want to have the result of methods serialized along side member attributes? Take a look at the :methods option).

You can also include first-level associations with :include:

1
2
3
4
5
6
7
8
9
user.to_xml(:except => [:id, :created_at], :include => :posts)
#=>
# <user>
#   <name>Ryan</name>
#   <email>ryan@spamme.com</email>
#   <posts>
#     <post><title>What's New in Edge Rails</title></post>
#   </posts>
# </user>

You can also manipulate the XML builder directly as to_xml now yields the builder, allowing you to add arbitrary elements to the resulting serialized XML representation:

1
2
3
4
5
6
7
8
9
user.to_xml(:except => [:id, :created_at]) do |xml|
  xml.serialize_version 1.1
end
#=>
# <user>
#   <name>Ryan</name>
#   <email>ryan@spamme.com</email>
#   <serialize_version>1.1</serialize_version>
# </user>

Note that the xml builder received in the block already contains the standard constructed XML representation – but you can now add to that with custom elements and other constructs. Just another easy way to massage the serialization to suit your needs.

tags: ruby, rubyonrails

Comments

Leave a response

  1. Jesse NewlandApril 13, 2007 @ 05:02 PM

    Oh, the joys of to_xml. A little pattern I’ve been using in ActiveRecord objects to ensure that sensitive information is hidden by default from my API users and follow DRY principles at the same time is this:

    
      alias_method :ar_to_xml, :to_xml
      def to_xml(options = {})
        default_except = [:crypted_password, :salt, :remember_token, :remember_token_expires_at, :created_at, :updated_at]
        options[:except] = (options[:except] ? options[:except] + default_except : default_except)   
        ar_to_xml(options)
      end
    

    This ensures all calls to_xml hide the sensitive bits by default, but can be plucked out by :only when needed.

  2. paulkavanMay 09, 2007 @ 01:08 PM

    Jesse, I liked your idea and modified it a tad to use the attr_protected attributes

    attr_protected :salt, :remember_token, :remember_token_expires_at
    alias_method :ar_to_xml, :to_xml
    def to_xml(options = {})
      # protect attributes registered with attr_protected
      default_except = self.class.protected_attributes()
      options[:except] = (options[:except] ? options[:except] + default_except : default_except)   
      ar_to_xml(options)
    end
  3. sjsMay 16, 2007 @ 03:32 AM

    I like all the ideas on this page, and came up with the following.

    lib/active_record_extensions.rb
    module ActiveRecordExtensions
      module Base
        def self.included(base)
          base.class_eval do
            alias_method_chain :to_xml, :safety
          end
        end
    
        def to_xml_with_safety(options = {})
          # protect attributes registered with attr_protected
          default_except = self.class.protected_attributes()
          options[:except] = (options[:except] ? options[:except] + default_except : default_except)   
          to_xml_without_safety(options)
        end
      end
    end
    

    Then make sure this gets run somewhere: ActiveRecord::Base.send(:include, ActiveRecordExtensions::Base)

  4. cmJuly 07, 2007 @ 07:13 PM

    Anyone seen a hack for to_xml to make it output the values as attributes instead of nested nodes? i.e person.to_xml = <person name="Bill Smith" id="4" />

  5. d3tAugust 15, 2007 @ 08:11 PM

    In any model you can over ride the XML as shown here

    http://api.rubyonrails.com/classes/ActiveRecord/XmlSerialization.html#M000910 (near the bottom)

    but instead of having xml.tag! ( ‘nodeName’, ‘nodeContent’) you can add attributes like this

    xml.tag!( ‘nodeName’, :attrib1 => ‘atribValue1’, :attrib2 => ‘attibValue2’, .. as many attributes u want)