Sunday, October 28, 2007

Goodbye respond_to, Hello provides

With the upcoming release of Merb 0.4.0 lets say goodbye to respond_to and hello to provides.

This isn't as bad as it might look at first glance. I remember when rails first brought out respond_to. I thought it was awesome, and it certainly has been a good, enabling method. Merb borrowed this along with a slew of other good ideas from rails, but today Merb transcends respond_to.

The provides method does a similar job to respond_to. It gives the user the format they want if it's available. It just does it a bit differently.

Lets have an example


class OldWay < Merb::Controller

def index
@items = Item.find(:all)
respond_to do |fmt|
fmt.html { render }
fmt.js { @items.to_json }
fmt.xml { render } # use a template
end
end

def show
@item = Item.find(:first)
respond_to do |fmt|
fmt.js { render } # use a template
fmt.xml{ @item.to_xml }
end
end

#...
end

I'm sure this kind of controller setup is reasonably familiar with anyone who is offering more than plain html to users.

Lets see this same example as it is with the new provides functionality.

class NewWay < Merb::Controller
provides :js, :xml

def index
@items = Item.find(:all)
render @items
end

def show
does_not_provide :html

@item = Item.find(:first)
render @item
end

#...
end

To my eye, that's much more concise.

This will respond to the user with the format they selected and the correct headers. But the good juju is not just that provides will select the format to return to the client. Render now accepts a model or ORM collection object as well. Say you call render with an object it will render a template if it finds one, otherwise it will attempt to call to_format method on the object.

What... No Blocks per Format

This behavior should cover a good proportion of cases without intervention, but if you need something more explicit you can fall back to good ol' ruby and use a case statement. The content_type method will give you the current format so you can be as fine grained as you like.

How to Control all this POWER?

There are a number of methods available.

provides :format1, :format2
only_provides :format1, :format3
does_not_provide :format6

These are available as both class an instance methods so you can combine them for very fine control.

Is it extensible?

What if you want to add your own format that a controller provides? Lets say you wanted to add an MS Word format for your controller. First add the mime type you want to merb specifying the format extension, transforming method, and possible accept headers.

Merb.add_mime_type(:doc, :to_doc, %w[application/vnd.ms-word])

This makes the doc format available for use in your controller

provides :doc

# ...

render @obj

to_doc will be called on @obj and the outgoing content-type header is set to application/vnd.ms-word. Using to_doc on a model is not the best idea, please remember I'm just using it as an example.