2008/12/05

First Ruby challenge

As I said in a previous post, I began an effort to learn some new technologies and languages. 5-10 minutes a day for each one (Cocoa, OpenGL and Ruby on Rails). Yesterday I had my first serious misunderstanding of Ruby. Rails controllers can output code in any format that client had requested: HTML, JSON, XML and so on. The way it lies the various output types is something like this:

respond_to do |format|
format.html { |x| puts x }
format.json { |x| puts x }
end

The inner part is understandable, since I knew code blocks from my Clipper 5 days. For each different format, a different code block is executed. But I simply couldn't understand the outer part. Was this do...end a loop? A disguised switch? Where "format" comes from? The "respond_to" seems to be a method, but the "parameter" would be the "do...end" code. Too strange.

And this pattern appears in several places in Rails scaffold-generated code. I couldn't let that pass without understanding how it really works.

After a lot of Googling, I finally got it at dawn. I have added some fake additional code to make it more understandable:

class Response
def html(&code_block)
# This response is HTML, so method does something
code_block.call("<html>...</html>")
end

def json(&code_block)
# This response is not JSON, so method does nothing
nil
end
end

def respond_to( &code_block )
code_block.call( Response.new )
end

respond_to do |format|
format.html { |x| puts x }
format.json { |x| puts x }
end

First, the do...end block that follows "respond_to" is not a loop, is not a disguised switch. It is simply a block of code that will be executed top to down, normally.

The magic here is: the do...end block is a code block exactly like {|x| ... }, which allows it to be passed as a parameter to respond_to method. So the do...end block is not executed right away, but only if respond_to does it.

Inside respond_to method, the code block is actually called, passing a Response object as a parameter. This Response object will be passed as "format" back to the code block. But it means that both Response.html and Response.json methods will be called in sequence, and we probably want only one of the formats in output.

The second magic is that Response object passed to code block is rigged to answer only to the method that is the desired output format. So, looking at my fake Response class, you can see that "html" method does something, while "json" method is a no-op. In Rails, of course this magic is done by the internal response dispatcher.

The third magic is that Rsponse.html method again receives a code block from user code -- in the case, {|x| puts x}, and calls it to render the HTML page the way the user wants to.

It seemed unnecessarly convoluted the first time I could see the whole picture, I would like to know the actual performance of that, but I guess people just get used to that, and the final syntax for different output formats is quite handy and terse.
blog comments powered by Disqus