XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

REST on Rails
by Matt Biddulph | Pages: 1, 2, 3

Introspection with ActiveRecord

Let's take a break from XML and look at how we can dynamically get data from any model object without prior knowledge of its fields. To understand fully how this works, I highly recommend firing up the Rails console (run script/console from the command line) on any Rails project and trying out the commands interactively. We'll show excerpts from an interactive session as we go through the concepts.

Every model is a subclass of ActiveRecord::Base, and this brings with it a number of essential methods for our task. The first is content_columns:

>> i = Item.find_first()
=> #<Item:0x2333814 @attributes={"title"=>"This is
an article's title", "id"=>"1", "bodytext"=>"The text of
the item"}>

>> i.class.content_columns
=> [#<ActiveRecord::ConnectionAdapters::Column:0x2331bcc
@type=:string, @default=nil, @limit=100, @name="title">,
#<ActiveRecord::ConnectionAdapters::Column:0x2331adc
@type=:string, @default=nil, @limit=250, @name="bodytext">

>> i['title']
=> "This is an article's title"

Notice that content_columns is a class method, so we invoke it as i.class.content_columns. It tells us that Items have two content columns of type string. We can loop through this list of column names in our XML template to get their values.

What if the model has associations? ActiveRecord has powerful automatic handling of database joins to manage relationships between models. These relationships are a key part of most Rails apps. ActiveRecord provides another useful class method:

>>
i.class.reflect_on_all_associations
=>
[#<ActiveRecord::Reflection::AssociationReflection:0x2337860
@macro=:belongs_to, @options={}, @name=:category,
@active_record=Category>]

This tells us that our Item has one association: it belongs to a Category. To find out what category our item belongs to, we just use one line of Ruby to resolve and call the category method given there, and we have all the data we need to make a serialization:

>> i.method('category').call
=> #<Category:0x22fb2e8
@attributes={"description"=>"Technical articles",
"id"=>"2" }>

Putting it all together, the RXML template looks like this:

xml.tag!(@obj.class.to_s.downcase,{:id =>
@obj.id}) {
    @obj.class.content_columns.each { |col|
       
xml.tag!(col.name,@obj[col.name])
    }
   
@obj.class.reflect_on_all_associations.each { |assoc|
       
if assoc.macro == :belongs_to || assoc.macro == :has_one
           
rels = [@obj.method(assoc.name).call]
       
end
       
if assoc.macro == :has_many || assoc.macro == :has_and_belongs_to_many
            #
*_many methods return lists
           
rels = @obj.method(assoc.name).call
       
end
       
rels.each { |rel|
           
if rel
               
name = rel.class.to_s.downcase
               
xml.tag!(name,{:id=>rel.id, :href
=>url_for(:only_path=>false,:action =>
name+"_xml",:id=>rel.id)})
           
end
        }
    }
}

Using this template and a little REXML XPath code as above, we can complete the code for dispatch in the controller.

Pages: 1, 2, 3

Next Pagearrow