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.
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
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.
I'd like to understand how the respond_to block actually works. What type of variable is format? Are .html and .json methods of the format object?
In order to understand what format
is, you could first look at the source for respond_to
, but quickly you'll find that what really you need to look at is the code for retrieve_response_from_mimes.
From here, you'll see that the block that was passed to respond_to
(in your code), is actually called and passed with an instance of Collector (which within the block is referenced as format
). Collector basically generates methods (I believe at Rails start-up) based on what mime types rails knows about.
So, yes, the .html
and .json
are methods defined (at runtime) on the Collector (aka format
) class.
The meta-programming behind responder registration (see Parched Squid's answer) also allows you to do nifty stuff like this:
def index
@posts = Post.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @posts }
format.csv { render :csv => @posts }
format.js
end
end
The csv line will cause to_csv to be called on each post when you visit /posts.csv. This makes it easy to export data as CSV (or any other format) from your rails site.
The js line will cause a javascript file /posts.js (or /posts.js.coffee) to be rendered/executed. I've found that to be a light-weight way to create an Ajax enabled site using jQuery UI pop-ups.