Let's pretend we have the following object:
class Widget
attr_accessor :id
attr_accessor :name
attr_accessor :gadgets
end
class Gadget
attr_accessor :id
attr_accessor :name
end
And we want this json from a widget:
{
id : 'widget-47',
name: 'name of widget',
children: [
{
id : 'gadget-63',
name : 'name of gadget'
}
]
}
Note that ID has the class name on the front, the name is passed through directly. Also, the sub-objects (Gadget) have their id processed.
First, let's make the Gadget presenter:
class GadgetPresenter
attr_reader :id
attr_reader :name
def initialize(gadget)
@id = %{gadget-#{gadget.id}}
@name = gadget.name
end
end
Rails provides Object extensions that give us a to_json method on every object. By default, this will call all of our attr_readers. So, all we have to do is provide attr_readers for the attributes we want in the json. Now, Gadget#to_json would give us:
{
id: 'gadget-63',
name: 'name of gadget'
}
Now the Widget presenter:
class WidgetPresenter
attr_reader :id
attr_reader :name
attr_reader :children
def initialize(widget)
@id = %{widget-#{widget.id}}
@name = widget.name
@children = widget.gadgets.collect{|g| GadgetPresenter.new(g)}
end
end
Pretty much the same, except for the children. This lets us call the associate "children" instead of "gadgets". And all we have to do is collect up a bunch of gadget presenters. When .to_json is called on the widget, it calls to_json on the attributes. Since children is an array, it collects .to_json from each entry in the array, which calls the GadgetPresenter's to_json.
I really like this implementation because it's really just an adapter, and it only concerns itself with the data. It is also external to the Widget and Gadget class, allowing for multiple types of presenters easily.
Here is a running example: (requires rails 3) https://gist.github.com/703747