[ANN] ActiveRecord .from_xml upgrade




The gist of this tiny code snip...


....is a light but flexible DSL that converts XML - typically output by to_xml()
- 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 were
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 <id> tag, then finds or creates an author with a
matching author.remote_id. Then the code updates the author.name, 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)
post.author = *node.from_xml(Author, :id, :name)

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

(Note, also, that <tag> records, for example, should be shared between many
<post> 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 string
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 lower-level
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 = n.data
post.tags << tag

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

Your block could have done something more complex, but this one merely simulated
form_tag{} by reconstituting and updating a Tag record, then inserted it into
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 "node.data".
So the little comment shows how to update all your Model attributes at once.

==what about to_xml?==

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


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question