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.