Social Media
More About This Website

My name is Wayne Robinson and I'm a web applications developer from Queensland, Australia. In August 2005 I discovered Ruby on Rails and instantly fell in love. From that point forward, Ruby on Rails has been my language of choice for new projects however, I still use PHP to maintain some legacy applications.

Categories
Login
« Efficiency of HTTP Push Vs Pull | Main | Good vs Great software developers. »
Monday
May012006

Ruby on Rails - ActiveRecord#build_from_xml function

I was playing with the new to_xml feature of Ruby on Rails and I found myself wondering... if you can create XML from ActiveRecord objects, why can't you create ActiveRecord objects from XML?

After searching for a while in the RoR Documentation I wasn't able to find the inverse functionality of to_xml. So now, it seems, I have an opportunity to contribute back to the Rails community with an a functional improvement of my own. I announce to you the build_from_xml method to ActiveRecord.

Just place the below code in your config/environment.rb file.


  require "rexml/document"
  module ActiveRecord
    class Base
      def self.build_from_xml(xml)
        xml = REXML::Document.new(xml) if xml.class == String

        ar = self.new
        xml.elements[1].elements.each do | ele |
          sym = ele.name.underscore.to_sym

          # An association
          if ele.has_elements?
            klass = self.reflect_on_association(sym).klass
            ar.__send__(sym) << klass.build_from_xml(ele)

          # An attribute
          else
            ar[sym] = ele.text
          end
        end

        return ar
      end
    end
  end

You can call this from the main class of any ActiveRecord object. Here is an example.

This ruby code:


  firm_xml = File.new("firm_data.xml").read
  firm = Firm.build_from_xml(firm_xml)

Will convert this XML file into a fully functional ActiveRecord object, including the associations.


  <firm>
    <rating type="integer">1</rating>
    <name>37signals</name>
    <clients>
      <client>
        <rating type="integer">1</rating>
        <name>Summit</name>
        <id type="integer">1</id>
        <firm-id type="integer">1</firm-id>
      </client>
      <client>
        <rating type="integer">1</rating>
        <name>Microsoft</name>
        <id type="integer">2</id>
        <firm-id type="integer">1</firm-id>
      </client>
    </clients>
    <accounts>
      <account>
        <id type="integer">1</id>
        <firm-id type="integer">1</firm-id>
        <credit-limit type="integer">50</credit-limit>
      </account>
    </accounts>
    <id type="integer">1</id>
  </firm>

You may have noticed one caveat. This function accepts well formed XML code only that conforms to your model. If it doesn't, it may produce unpredictable results but will probably raise the usual ActiveRecord exceptions in most non-trivial error cases. Oh, and it requires REXML, but you knew that already right.

I will probably convert this to a plugin in the not-to-distant future. That is if the code isn't included in Rails' release branch (hint, hint).

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments (10)

Great. I've been doing this manually for each model I create. This is much more DRY!
May 2, 2006 | Unregistered CommenterRyan C.
This is great! Thank you Wayne :)
July 8, 2006 | Unregistered CommenterRemi
Hi Wayne,
Thank you that is very useful I was just getting to writing something similar when I discovered this. A great start!

I have enhanced the code to deal with:-

* Records that have :null => false, so that their default is set if XML has no value, and :null=> false for that particular column.
* Ignoring relations defined in the XML that aren't part of the models
* Forcing XML names to match with object names
* XML data containing multiple record data (returns a list of instances)
* Also I had trouble actually getting the relations working, so have re-done that code, I think it definetly works now

I have posted the code on my website:-
http://riftor.g615.co.uk/content.php?view=50&type=1
August 25, 2006 | Unregistered CommenterDominic Orchard
Hi,

Great piece of code!

Does anyone know if it's possible to to use to_xml and build_from_xml with 3 levels of associations, like:

--------------------------------------
class Level1 < ActiveRecord::Base
has_many :level2s
end

class Level2 < ActiveRecord::Base
belongs_to :level1
has_many :level3s
end

class Level3 < ActiveRecord::Base
belongs_to :level2
End

--------------------------------------

It would be great to be able to move in XML whole pieces of data, whatever structure depth they have.

Cheers,

Phlippe
August 31, 2006 | Unregistered CommenterPhilippe Lang
Philippe,

to_xml does not to 3 levels of association but Wayne's build_from_xml should do (if its associations code is working), and mine certainly does do any depth.
September 4, 2006 | Unregistered CommenterDominic Orchard
First of all - to_xml supports 3 levels of association. Example:
@test.to_xml(:skip_instruct => true, :except => [ :id, :user_id ], :include => { :questions => {:except => [:question_id, :id], :include => [:answers]}, :interpretations => {}})

first level - test
second level - question
third level - answer

I tried to use "build_to_xml" and it restore only one object from each level (((
June 21, 2007 | Unregistered Commenterbioform
I made small changes:

require "rexml/document"
module ActiveRecord
class Base
def self.build_from_xml(xml)
xml = REXML::Document.new(xml).elements[1] if xml.class == String

ar = self.new
xml.elements.each do | ele |
sym = ele.name.underscore.to_sym
# An association
if ele.has_elements?
klass = self.reflect_on_association(sym).klass
ele.elements.each do | obj |
ar.__send__(sym) << klass.build_from_xml(obj)
end
# An attribute
elsif !ele.text.nil?
ar[sym] = ele.text
end
end

return ar
end
end
end
June 21, 2007 | Unregistered Commenterbioform
Has this functionality been included in any Rails release till now? I would be grateful if you could reply directly via mail.

If not, I'll use your code or Dominic's modified version. But it would be great if this is either included in the Rails base or distributed as a plugin.

Thanks for this anyways.
December 17, 2007 | Unregistered CommenterSaurabh Nanda
Do we need to have relevant tables in database for getting ActiveRecord object from xml?
March 7, 2008 | Unregistered CommenterSwapnil
Look at that. A way back precursor to nested attributes.
May 19, 2009 | Unregistered CommenterIan

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
All HTML will be escaped. Hyperlinks will be created for URLs automatically.