You probably remember doing the following in Rails 2 :
respond_to do |format|
format.html
format.json { render :json => @object }
end
In rails 3, you can replace those calls with :
respond_with @object
The first solution is still working of course. The idea with respond_with is that in 99% of the cases, you'll do the same thing, depending of the format. In HTML, we render the content of a template. In any other format, we render the parsed content (in json or xml for example).
However, you might need to do some different things.
Let's guess, for example, that you wish to render your page in iCal.
With respond_to, you'd just add the format and define inside the blog what needs to be done.
With respond_with, it's not possible anymore as you don't have direct control of what's happening.
However, of course, you can use responders which will allow us to do what we expect to get.
responder, what's that ?
As you can guess, when you use respond_with, rails knows what do to with your object, depending of the request's format.
The responder is the component which knows what to do.
In order to render our application differently, we'll just have to create our very own responder.
Let's look at the internal rails' responder :
module ActionController
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
def initialize(controller, resources, options={})
@controller = controller
@request = @controller.request
@format = @controller.formats.first
@resource = resources.last
@resources = resources
@options = options
@action = options.delete(:action)
@default_response = options.delete(:default_response)
end
def self.call(*args)
new(*args).respond
end
def respond
method = "to_#{format}"
respond_to?(method) ? send(method) : to_format
end
def to_html
default_render
rescue ActionView::MissingTemplate => e
navigation_behavior(e)
end
def to_format
default_render
rescue ActionView::MissingTemplate => e
api_behavior(e)
end
end
end
I only kept what we need for the purpose of this article. You can find the entire file at action_controller/metal/responder.rb.
Our responder will be called via it's call method, which will initialize the object and call it's method respond.
If you wich to create your own responder, you just need an object which responds to self.call.
In the default responder, we can see, in the respond method that, if a to_xx method is defined, we call it. That's for HTML.
Otherwise, we use to_format.
In both cases, the default rendering will be the same. The default_render method will try to find a template in your views.
When there's no view, the behavior or quite different.
In HTML, it render an error. We can't display anything without a view ! In any other format, we'll render the object in the appropriate format.
So, in the api_behavior method, we have :
def api_behavior(error)
raise error unless resourceful?
if get?
display resource
elsif has_errors?
display resource.errors, :status => :unprocessable_entity
elsif post?
display resource, :status => :created, :location => api_location
elsif has_empty_resource_definition?
display empty_resource, :status => :ok
else
head :ok
end
end
We can see here that, depending of the request's format (get, post) and whether the object has validation errors or not, we render the content differently (and we render it with a different HTTP status code).
That's what, in a rails 2 application, we'd have done the following way :
respond_do do |format|
format.json do
if @object.save
render :json => @object
else
render :json => @object, :status => :unprocessable_entity
end
end
end
That kind of code was repeated in all our controllers. We don't need to do it anymore !
Ok, that's cool. But what can I do exactly ?
That presentation is nice. But what can we really do with responders ?
In the Crafting Rails Applications book, José Valim gets into the details of responders and gives several examples.
You can find them on github : github.com/plataformatec/responders.
There is, for example, the HTTP Cache Responder. It overrides the to_format method.
def to_format
return if do_http_cache? && do_http_cache!
super
end
And adds two methods : do_http_cache? and do_http_cache!
def do_http_cache!
timestamp = resources.flatten.map do |resource|
resource.updated_at.try(:utc) if resource.respond_to?(:updated_at)
end.compact.max
controller.response.last_modified ||= timestamp if timestamp
head :not_modified if fresh = request.fresh?(controller.response)
fresh
end
def do_http_cache?
get? && @http_cache != false && ActionController::Base.perform_caching && persisted? && resourceful?
end
We can see in the second one that we accept caching if we're on a GET request, the cache is activated and the resource exists in the database.
In the first method, we take the last modification date and we compare it with the last_modified HTTP header from the request.
If it's "fresh" (the object hasn't been modified since it's was last viewed), we can send a head :not_modified.
In 40 lignes of code, this responder implements an HTTP cache on all the non-HTML calls of your application !
In the same kind, on a "top secret" project, I wanted to be able to have different jsons for the same object, depending of the current user.
It allows me to display only some fields in the json, depending of whether the user can see the information or not.
So I wanted to know who is the current user in my model's serializable_hash method.
This method is used to get a hash from a model and send it in any format (json, xml, ...).
So I did the HashParameters responder.
With that responder, in my controller, I can now define a method serializable_hash_parameters, which return a hash.
The parameters for this hash will be provided to my model.
I'm doing it in the internal method display.
def display(resource, given_options={})
given_options.merge!(controller.send(:serializable_hash_parameters)) if controller.respond_to?(:serializable_hash_parameters, true)
super(resource, given_options)
end
And how can I activate my responder ?
In my HashParameters example, you can see that I define a module named "Responder", which I include in my ApplicationController.
This module will allow me to include several responders without having to worry about updating my controller.
By doing that, I can decide to export my responders to an external gem and reuse them in an other application.
There is my Responder module :
module Responder
class ClassMethods < ActionController::Responder
include ::Responder::HashParameters
end
def self.included(klass)
klass.class_eval do
def self.responder
Responder::ClassMethods
end
end
end
end
When it's included, the self.included method will override the self.responder method from my controller, therefor changing my application's responder.
I set as responder the Responder::ClassMethods class, in which I include the "HashParameters" module.
As of now, my controller's responder is the one I just configured !
Conclusion
As you have seen, responders allows us to fundamentally change the behavior of our application and helps us remain particularly DRY.
Don't be afraid to user them ! And if you've not yet migrated to rails 3, this feature by itself should be a hell of an argument in favor of doing it.


Comments