This is how I’m doing it, anyway. I’ll leave it up to the experts to explain how to do it properly.
I have a controller called (say), ‘hats_controller.rb’, which contains the following:
class HatsController < ApplicationController
...
def hatsml
@hats = Hats.find(:all)
render :layout => false
end
. . .
end
Note that @hats is populated before anything else. If you had the Paginator helper running, here’s where you override it. I also have a RXML template in ‘/views/hats/hatsml.rxml’. (In Rails 2.0+ this would be ‘/views/hats/hatsml.xml.builder’):
xml.instruct! :xml, :version=>"1.0"
xml.instruct! :rss, :version=>"2.0"
xml.channel{
for hat in @hats
xml.hat do
xml.title(hat.name)
xml.description(hat.description)
end
end
}
And then I can see the output at ‘/hats/hatsml’. Nice.
RXML files use Builder (http://builder.rubyforge.org/).
Also note, if you’ve scaffolded the CRUD for your object, you’ll want to remove the lines where it formats the XML automatically:
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @hats }
end
Change that to so something like:
respond_to do |format|
format.html # index.html.erb
format.xml # index.xml.builder
end
respond_to block (in Rails 2.0 you don’t need it)
See the Railscast on generating RSS for a good intro to all of this.
It is generally a good idea to serve your XML responses with a proper contenttype defined in the headers. See HowtoChangeTheContentType.
For my application, I return a list of foods, each of which as one or more portions. Building on the hats example, I ended up with this:
MODELS:
class Food < ActiveRecord::Base
has_many :portions
end
class Portion < ActiveRecord::Base
belongs_to :food
end
CONTROLLER:
class InvoiceController < ApplicationController
def list_foods
@foods = Food.find(:all, :include => [:portions])
end
end
VIEW:
xml.instruct! :xml, :version=>"1.0"
xml.foods{
for food in @foods
xml.food do
xml.id(food.id)
xml.calories(food.calories)
xml.description(food.description)
xml.short_description(food.short_description)
for portion in food.portions
xml.portion do
xml.grams(portion.grams)
xml.amount(portion.amount)
xml.serving(portion.serving)
end
end
end
end
}
Which creates XML like this:
<foods> <food> <id>9193</id> <calories>115</calories> <description>Olives, ripe, canned (small-extra large)</description> <short_description>Olives,Ripe,Cnd (Small-Extra Lrg)</short_description> <portion> <grams>8.4</grams> <amount>1.0</amount> <serving>tbsp</serving> </portion> <portion> <grams>4.4</grams> <amount>1.0</amount> <serving>large</serving> </portion> <portion> <grams>3.2</grams> <amount>1.0</amount> <serving>small</serving> </portion> </food> <food> <id>9194</id> <calories>81</calories> <description>Olives, ripe, canned (jumbo-super colossal)</description> <short_description>Olives,Ripe,Cnd (Jumbo-Super Colossal)</short_description> <portion> <grams>8.3</grams> <amount>1.0</amount> <serving>jumbo</serving> </portion> <portion> <grams>15.0</grams> <amount>1.0</amount> <serving>super colossal</serving> </portion> </food> </foods>
Remember to append the dump Ruby string method on any text that might have special characters in it, as they can cause some browsers cough IE to complain in stereotypically opaque ways.
XML attributes can be added like this:
xml.link :rel=>'self',
:href=>url_for(:only_path=>false, :action=>'posts', :path=>['index.atom'])
For a tag with both attributes and content:
xml.a "the link text here", :href => "/your/url" do