Modify the XML output from your Model
So your app needs to generate XML. No problem, ActiveRecord gives you it for free. Simply call mymodel.to_xml and your done. But what happens if you need to generate more complicated…specialized XML? There are a few options:
- Don’t call to_xml, generate the XML using a template (.rxml)
- Override the to_xml method. As mentioned in the docs
- Create a separate method for generating the XML
To keep things simple, and for reasons we’ll see later, let’s use 3.
The example
Ok. I have a model, Car, with 3 attributes (year,make,model). Here’s what the default XML looks like:
car = Car.find(:first) car.to_xml => <car> <make>Nissan</make> <model>Pickup</make> <year>1995</year> </car>
So let’s customize the XML to add a namespace for the elements and change the tag type. In the Car model we’ll create a new method called my_xml instead of overriding to_xml:
def my_xml(options={})
options[:indent] ||= 2
xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
xml.instruct! unless options[:skip_instruct]
xml.mycar(:Vehicle, "xmlns:mycar" => "http://crazystuff.org/car/ns") do
xml.mycar(:make, self.make)
xml.mycar(:model, self.model)
xml.mycar(:year, self.year)
end
end
The Model uses the Builder library for creating the XML. That was easy. Now when I call car.my_xml I get this:
<?xml version="1.0" encoding="UTF-8"?> <mycar:Vehicle xmlns:mycar="http://crazystuff.org/car/ns"> <mycar:make>Nissan</mycar:make> <mycar:model>Pickup</mycar:model> <mycar:year>1995</mycar:year> </mycar:Vehicle>
Perfect! Now let’s try and query all Cars and see what we get:
all_cars = Car.find(:all) all_cars.to_xml => NoMethodError: undefined method 'my_xml_' for #<Array:0x1379810>
What the *$%@! That’s not right. Calling Car.find(:all) returns an Array. Array doesn’t have a method my_xml.
But how does Rails do it? If “all_cars” is an Array, then Array within Rails must support the to_xml method. As it turns out Rails adds some tricks to some of the core pieces of the Ruby language. Of interest to us right now is the module ActiveSupport::CoreExtensions::Array::Conversions. It defines a to_xml method that is a mixin for the Array class.
We could open up the Module and change it. Or we could just create our own method and include it into Array. Let’s do something like that:
module MyConversion
def my_xml
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
# TODO: Move the xmlns from Vehicle to here...
options[:builder].tag!("mycar:AllVehicles") do
# Here's we loop over each entry (model) and call it's my_xml
each { |e| e.my_xml(options.merge!({ :skip_instruct => true })) }
end
end
end
# Don't forget to do this!
class Array
include MyConversion
end
Ok. That’s it. Now when we call my_xml regardless of whether it’s a Array or a single object it works as expected.
Have a look around in the ActiveSupport Core Ext. There’s a lot to learn there.
Trackbacks
Unfortunately, due to spammers I've had to close both trackbacks and comments.