views:

70

answers:

4

Would someone be able to break down the Ruby specifics of what each of these statements consist of in as far as methods, parameters, block interpretations etc. This is very common to see in Rails code and I'm trying to understand how the Ruby interpreter reads this code:

respond_to do |format|
  format.xml { render :layout => false }
end

In as far as I understand, respond_to is a method that's taking one parameter to it, a block. So I'm guessing it's written something like:

def respond_to(&block)
    block.call
end

.. or something similar?

in the block itself, format is the object respond_to passes into the block and xml is what the request is set to, at which point it calls a block in itself if the request is asking for XML type data and goes ahead and invokes a render method, passing it a keyword based argument, :layout => false?

Would someone clean up my understanding of how they above works. This type of code is all over Rails and I'd like to understand it before using it more.

+1  A: 

This is a typical Implementation Pattern for Internal DSLs in Ruby: you yield an object to the block which then itself accepts new method calls and blocks and thus guides the interface. (Actually, it's pretty common in Java, too, where it is used to get meaningful code completion for Internal DSLs.)

Here's an example:

def respond_to
  yield FormatProxy.new(@responders ||= {})
end

class FormatProxy
  def initialize(responders)
    @responders = responders
  end

  def method_missing(msg, *args, &block)
    @responders[msg] = [args, block]
  end
end

Now you have a mapping of formats to executable pieces of code stored in @responders and you can call it later and in a different place, whenever, whereever and however often you want:

respond_to do |f|
  f.myformat { puts 'My cool format' }
  f.myotherformat { puts 'The other' }
end

@responders[:myformat].last.call # => My cool format
@responders[:myotherformat].last.call # => The other

As I hinted at above, if instead of a dumb proxy object that simply uses method_missing, you were to use one which had the most important methods (xml, html, json, rss, atom and so on) predefined, a sufficiently intelligent IDE could even give you meaningful code completion.

Note: I have absolutely no idea whether this is how it is implemented in Rails, but however it is implemented, it is probably some variation of this.

Jörg W Mittag
A: 

If you have such kind of questions or not sure how things work, the best way to find it out is to go to the source (which in Ruby is very readable).

For this particular question, you can go to mime_respond.rb. Line 187 ATM.

The comment explains:

# Here's the same action, with web-service support baked in:
#
#   def index
#     @people = Person.find(:all)
#
#     respond_to do |format|
#       format.html
#       format.xml { render :xml => @people.to_xml }
#     end
#   end
#
# What that says is, "if the client wants HTML in response to this action, just respond as we
# would have before, but if the client wants XML, return them the list of people in XML format."
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)

Additionally respond_to takes a block OR mime types to respond with.

I would really recommend to have a look at the code there.
The comments are very comprehensive.

Dmytrii Nagirniak
A: 
def respond_to(&block)
    block.call
end

This is the definition of a method with one parameter. The & tells the interpreter that the parameter may also be given, when we're gonna' call the method, in the block do ... end form: respond_to do puts 1 end. This parameter can also be any object that responds to a call metod (like a Proc or a lambda):
a = lambda{ puts 1 }; respond_to(a)

respond_to do |format|
   format.xml { render :layout => false }
end

This calls the respond_to method with one parameter, the do ... end block. In the implementation of this second respond_to method this block is called similar to the following:

def respond_to(&block)
    block.call(@format) # or yield @format
end

so in order to conform to the 1 parameter call, our block of code must also accept 1 parameter, which in the do ... end' syntax is given between the bars|format|`

clyfe
+1  A: 

You've basically got it, and the source code is readable enough to figure out what's going on. When you want to source dive something like this, there are only a few steps needed.

1. Figure out where Rails is.

$ gem environment

RubyGems Environment:
  - RUBYGEMS VERSION: 1.3.5
  - RUBY VERSION: 1.8.7 (2009-06-12 patchlevel 174) [i686-darwin9.8.0]
  - INSTALLATION DIRECTORY: /opt/ruby-enterprise-1.8.7-2009.10/lib/ruby/gems/1.8
  ...

2. Figure out where in Rails the code is.

$ cd /opt/ruby-enterprise-1.8.7-2009.10/lib/ruby/gems/1.8/gems  # installation directory from above + "/gems"

$ ack "def respond_to"

...
actionpack-2.3.5/lib/action_controller/mime_responds.rb
102:      def respond_to(*types, &block)
...

3. Dive in.

$ vim actionpack-2.3.5/lib/action_controller/mime_responds.rb
jdl
Or just control-click the method in netbeans.
clyfe
I haven't used NetBeans in a couple of years, because the editor drives me bonkers, but that certainly sounds like a useful feature.
jdl
If you want to dive into Rails sourcecode, Rails 3 is absolutely awesome: much cleaner, much more modular, much simpler, much more straightforward, all dependencies clearly spelled out.
Jörg W Mittag
Definitely good to know. I've played with Rails 3, but I'm busy enough with Rails 2 code that I haven't had time to jump in yet.
jdl