Rails: How does the respond_to block work?
I am new to Ruby and got stuck at this same code. The parts that I got hung up on were a little more fundamental than some of the answers I found here. This may or may not help someone.
respond_to
is a method on the superclassActionController
.- it takes a block, which is like a delegate. The block is from
do
untilend
, with|format|
as an argument to the block. - respond_to executes your block, passing a Responder into the
format
argument.
http://api.rubyonrails.org/v4.1/classes/ActionController/Responder.html
- The
Responder
does NOT contain a method for.html
or.json
, but we call these methods anyways! This part threw me for a loop. - Ruby has a feature called
method_missing
. If you call a method that doesn't exist (likejson
orhtml
), Ruby calls themethod_missing
method instead.
http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_2.html
- The
Responder
class uses itsmethod_missing
as a kind of registration. When we call 'json', we are telling it to respond to requests with the .json extension by serializing to json. We need to callhtml
with no arguments to tell it to handle .html requests in the default way (using conventions and views).
It could be written like this (using JS-like pseudocode):
// get an instance to a responder from the base class
var format = get_responder()
// register html to render in the default way
// (by way of the views and conventions)
format.register('html')
// register json as well. the argument to .json is the second
// argument to method_missing ('json' is the first), which contains
// optional ways to configure the response. In this case, serialize as json.
format.register('json', renderOptions)
This part confused the heck out of me. I still find it unintuitive. Ruby seems to use this technique quite a bit. The entire class (responder
) becomes the method implementation. In order to leverage method_missing
, we need an instance of the class, so we're obliged to pass a callback into which they pass the method-like object. For someone who has coded in C-like languages for 20 some years, this is very backwards and unintuitive to me. Not that it's bad! But it's something a lot of people with that kind of background need to get their head around, and I think might be what the OP was after.
p.s. note that in RoR 4.2 respond_to
was extracted into responders gem.
From what I know, respond_to is a method attached to the ActionController, so you can use it in every single controller, because all of them inherits from the ActionController. Here is the Rails respond_to method:
def respond_to(&block)
responder = Responder.new(self)
block.call(responder)
responder.respond
end
You are passing it a block, like I show here:
respond_to <<**BEGINNING OF THE BLOCK**>> do |format|
format.html
format.xml { render :xml => @whatever }
end <<**END OF THE BLOCK**>>
The |format| part is the argument that the block is expecting, so inside the respond_to method we can use that. How?
Well, if you notice we pass the block with a prefixed & in the respond_to method, and we do that to treat that block as a Proc. Since the argument has the ".xml", ".html" we can use that as methods to be called.
What we basically do in the respond_to class is call methods ".html, .xml, .json" to an instance of a Responder class.
This is a block of Ruby code that takes advantage of a Rails helper method. If you aren't familiar with blocks yet, you will see them a lot in Ruby.
respond_to
is a Rails helper method that is attached to the Controller class (or rather, its super class). It is referencing the response that will be sent to the View (which is going to the browser).
The block in your example is formatting data - by passing in a 'format' paramater in the block - to be sent from the controller to the view whenever a browser makes a request for html or json data.
If you are on your local machine and you have your Post scaffold set up, you can go to http://localhost:3000/posts
and you will see all of your posts in html format. But, if you type in this: http://localhost:3000/posts.json
, then you will see all of your posts in a json object sent from the server.
This is very handy for making javascript heavy applications that need to pass json back and forth from the server. If you wanted, you could easily create a json api on your rails back-end, and only pass one view - like the index view of your Post controller. Then you could use a javascript library like Jquery or Backbone (or both) to manipulate data and create your own interface. These are called asynchronous UIs and they are becomming really popular (Gmail is one). They are very fast and give the end-user a more desktop-like experience on the web. Of course, this is just one advantage of formatting your data.
The Rails 3 way of writing this would be this:
class PostsController < ApplicationController
# GET /posts
# GET /posts.xml
respond_to :html, :xml, :json
def index
@posts = Post.all
respond_with(@posts)
end
#
# All your other REST methods
#
end
By putting respond_to :html, :xml, :json
at the top of the class, you can declare all the formats that you want your controller to send to your views.
Then, in the controller method, all you have to do is respond_with(@whatever_object_you_have)
It just simplifies your code a little more than what Rails auto-generates.
If you want to know about the inner-workings of this...
From what I understand, Rails introspects the objects to determine what the actual format is going to be. The 'format' variables value is based on this introspection. Rails can do a whole lot with a little bit of info. You'd be surprised at how far a simple @post or :post will go.
For example, if I had a _user.html.erb partial file that looked like this:
_user.html.erb
<li>
<%= link_to user.name, user %>
</li>
Then, this alone in my index view would let Rails know that it needed to find the 'users' partial and iterate through all of the 'users' objects:
index.html.erb
<ul class="users">
<%= render @users %>
</ul>
would let Rails know that it needed to find the 'user' partial and iterate through all of the 'users' objects:
You may find this blog post useful: http://archives.ryandaigle.com/articles/2009/8/6/what-s-new-in-edge-rails-cleaner-restful-controllers-w-respond_with
You can also peruse the source: https://github.com/rails/rails