ActiveRecord .from_xml upgrade


The gist of this tiny code snip…

…is a light but flexible DSL that converts XML - typically output by

  • into an ActiveRecord object model.

==create or update records==

Here’s the simplest example:

xml ='<photos>

doc = Nokogiri::XML(xml)
photos = doc.from_xml(Photo, :id)

(Note that from_xml{} is a member of a Node, not of your Model.)

That code created new Photo records with matching IDs. If any record
already there, the code would update it instead.

==rename fields and pass in data==

Here’s the next more complicated example:

authors = node.from_xml(Author, [:id, :remote_id], :name)

The code reads an tag, then finds or creates an author with a
matching author.remote_id. Then the code updates the, and
return an array of authors.


from_xml takes an optional &block, and yields into this the record under
construction, before its .save! call. Use this block to seek nested
data, and
plug them into their parent record:

doc.from_xml Post, :id, :title, :body do |post, node, *|
  post.tags = node.from_xml(Tag, :id, :name) = *node.from_xml(Author, :id, :name)!

from_xml{} will call that block each time it finds a (top-level)
and each nested node.from_xml{} call will only find records inside that
main record.

(Note, also, that records, for example, should be shared between
records, and your XML will probably just duplicate them many
times, but
from_xml(Tag) knows to fold them all back together again…)

The splat operator * threw away three more arguments - they were the
values of the id, title, and body fields.

==raw XML==

To scan your XML with very similar abilities, but without using a Model
with the
correct name to match your XPath, write the XPath directly into the
convert{} method:

  node.convert 'tags/tag', :id, :name do |n, id, name|
    tag = Tag.find_or_initialize_by_id(id)
    tag.update_attribute :name, name
     # or tag.attributes =
    post.tags << tag

That block shows form_tag{} “unrolled” into its low-level behavior.
takes an XPath query, relative to the current node, and a list of fields
their renamers) to extract. Then it yields the detected node (don’t call
“node”!) into its |goal posts|, with the string value of each detected

Your block could have done something more complex, but this one merely
form_tag{} by reconstituting and updating a Tag record, then inserted it
some outer post object.

One more detail - the renamed fields, and their string values, are also
available as a hash. To avoid even more extra arguments into our goal
posts, the
committee stashed them into the passed node, as an attribute called
So the little comment shows how to update all your Model attributes at

==what about to_xml?==

One ActiveRecord FAQ goes, “Why can’t from_xml take the same arguments
to_xml?” The reason is creation is harder than just reading an existing
model. While a future version of from_xml{} could indeed learn to follow
associations, and could take a big blob of nested hashes, like most
ActiveRecord methods, the committee does not foresee this DSL exactly
the input to to_xml(). That is a goal for further research on both

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs