Ruby syntax in "respond_to do |format| line -- can clarify?

J

Joshua Beall

Hi All,

I'm new to both Ruby and Rails (I come from an ASP.NET/C# background,
mainly, and I also have done a fair amount of PHP). I'm looking at
these lines from my controller:

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end

And I just can't quite get my head around it completely. I know the end
result is that it renders different types of output. But I don't
understand how it's executing. I read the docs here:
http://www.cs.auckland.ac.nz/references/ruby/doc_bundle/Manual/man-1.4/syntax.html

Which states:

" method_call do [`|' expr...`|'] expr...end

The method may be invoked with the block (do .. end or {..}). The method
may be evaluate back that block from inside of the invocation. The
methods that calls back the blocks are sometimes called as iterators.
The evaluation of the block from iterator is done by yield. "

But I still don't understand where |format| is coming from, or what it's
doing. From what I've seen in Ruby, I think of something like |format|
as being part of a foreach type block. As in "possible_formats.each do
|format|".

So this is what I'm seeing in my head when I look at that block:

for(format in ????)
respond_to(format.html) # index.html.erb will be rendered by default
respond_to(format.xml , { render :xml => @posts })
end

But that can't be right, because there's nothing for ???? to be resolved
to...

Can someone help clear this up for me?
 
A

Andrew Stewart

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end

But I still don't understand where |format| is coming from, or what
it's
doing. From what I've seen in Ruby, I think of something like |
format|
as being part of a foreach type block. As in
"possible_formats.each do
|format|".

Take heart, the code isn't entirely obvious if you're unfamiliar with
Ruby!

The format between the pipes ("|format|") at the start of the block
is a block variable. It could be called anything though the
convention is to use "format". When respond_to was first introduced
lots of people used "wants" to make the code in the block read nicely
and it almost prevailed.

Anyway, the block variable is set within the respond_to method:

http://dev.rubyonrails.org/browser/trunk/actionpack/lib/
action_controller/mime_responds.rb#L102

module ActionController
module MimeResponds
module InstanceMethods

def respond_to(*types, &block)
raise ArgumentError, "respond_to takes either types or a
block, never both" unless types.any? ^ block
block ||= lambda { |responder| types.each { |type|
responder.send(type) } }
responder = Responder.new(self)
block.call(responder)
responder.respond
end

class Responder
# ...
end

end
end
end

The &block in the respond_to method's signature is set to the
do...end block from your snippet. The first two lines of the method
are irrelevant to your situation. The third line creates an object
that can respond appropriately to different mime types. The fourth
line is the one you're interested in: it executes the block, passing
in the responder object.

So if you look at your block again:

do |format|
format.html
format.xml
end

...and you imagine executing it as a method, the block variable,
format, should look like a method argument. Accordingly it is set to
the responder object passed in by the line:

block.call(responder)

Thus the format.html and format.xml lines in the block are
sending :html and :xml messages to the object in format, i.e. to
responder. As it happens responder doesn't have an html method, nor
an xml method, so these calls are handled by its method_missing method.

http://dev.rubyonrails.org/browser/trunk/actionpack/lib/
action_controller/mime_responds.rb#L139

I recommend reading up on blocks and procs. Here's one article to
get you started:

http://onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc

Regards,
Andy Stewart
 
A

Andrew Timberlake

I'm new to both Ruby and Rails (I come from an ASP.NET/C# background,
mainly, and I also have done a fair amount of PHP). I'm looking at
these lines from my controller:

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml =3D> @posts }
end

And I just can't quite get my head around it completely. I know the = end
result is that it renders different types of output. But I don't
understand how it's executing. I read the docs here:
= http://www.cs.auckland.ac.nz/references/ruby/doc_bundle/Manual/man-1.4/sy=
ntax.html

But I still don't understand where |format| is coming from, or what = it's
doing. From what I've seen in Ruby, I think of something like = |format|
as being part of a foreach type block. As in "possible_formats.each = do
|format|".

So this is what I'm seeing in my head when I look at that block:

for(format in ????)
respond_to(format.html) # index.html.erb will be rendered by default
respond_to(format.xml , { render :xml =3D> @posts })
end

But that can't be right, because there's nothing for ???? to be = resolved
to...

Can someone help clear this up for me?

Joshua

respond_to is the method, it provides the block with the format object.
Within the block, you are calling the methods html and xml on the format =
object supplied.
Rather than a loop, the block is more like a callback function that =
accepts a format object as a parameter.
The format object now understands that it can respond with either html =
or xml output.
Rails then uses the Accepts header of the calling client (browser etc) =
to decide which of the formats to return.
You can also append .html or .xml to the url to force one or the other =
format.

Hope that helps

Andrew Timberlake
(e-mail address removed)
082 415 8283
skype: andrewtimberlake

"I have never let my schooling interfere with my education."
--Mark Twain
 
J

Joshua Beall

Andrew said:
On 17 Jan 2008, at 16:21, Joshua Beall wrote:
So if you look at your block again:

do |format|
format.html
format.xml
end

...and you imagine executing it as a method, the block variable,
format, should look like a method argument. Accordingly it is set to
the responder object passed in by the line:

block.call(responder)

Ok, I think I'm with you. Correspondingly that would mean that I can
replace this

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end

With this:

responder = Responder.new(self)
responder.html
responder.xml { render :xml => @posts}
responder.respond

And it should work. And indeed it does. However, there are still
somethings I don't understand. Why can't I do this?

responder = Responder.new(self)
responder.html
responder.xml( { render :xml => @posts} )
responder.respond

If I do that, I get the error (point to the line where I added
parentheses):

posts_controller.rb:9: syntax error, unexpected tSYMBEG, expecting kDO
or '{' or '('

It's complaining about the symbol :xml.

Second, from this line here:
http://dev.rubyonrails.org/browser/trunk/actionpack/lib/action_controller/mime_responds.rb#L102

Can you help me understand this line:
block ||= lambda { |responder| types.each { |type| responder.send(type)
} }

It looks like it would be overwriting the "block" parameter of the
"respond_to" method -- and yet apparently it is not?
 
J

Justin Collins

Joshua said:
Ok, I think I'm with you. Correspondingly that would mean that I can
replace this

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end

With this:

responder = Responder.new(self)
responder.html
responder.xml { render :xml => @posts}
responder.respond

And it should work. And indeed it does. However, there are still
somethings I don't understand. Why can't I do this?

responder = Responder.new(self)
responder.html
responder.xml( { render :xml => @posts} )
responder.respond

If I do that, I get the error (point to the line where I added
parentheses):

posts_controller.rb:9: syntax error, unexpected tSYMBEG, expecting kDO
or '{' or '('

It's complaining about the symbol :xml.
In this case { render :xml => @posts} is being interpreted as a Hash, not a block and that is messing things up for you.

Ruby doesn't seem sure about how it should parse this for you. It could be

{render:)xml) => @posts}

or

{render:)xml => @posts)} #throws a different error

In any case, it still thinks it is a Hash.

-Justin
 
T

Todd Benson

Joshua

respond_to is the method, it provides the block with the format object.
Within the block, you are calling the methods html and xml on the format object supplied.
Rather than a loop, the block is more like a callback function that accepts a format object as a parameter.
The format object now understands that it can respond with either html or xml output.
Rails then uses the Accepts header of the calling client (browser etc) to decide which of the formats to return.
You can also append .html or .xml to the url to force one or the other format.

Hope that helps

Andrew Timberlake
(e-mail address removed)
082 415 8283
skype: andrewtimberlake

"I have never let my schooling interfere with my education."
--Mark Twain

To illustrate this in a _very_ simple way...

# the following method is akin to what is already defined by rails
(#respond_to, which is much more complicated, of course)
def foo( &block )
block.call( 42 )
end

# the code you execute
foo do |number| # 42 is thrown into your block
puts number
puts number * 2
end

In a similar way, the object named "format" (or whatever you want to
name it) is thrown into your block by the framework.

At least, that's how I think rails handles it. I've never used the
method recently, myself, and unfortunately I don't have rails handy at
the moment.

Todd
 
J

Joshua Beall

Justin said:
In this case { render :xml => @posts} is being interpreted as a Hash,
not a block and that is messing things up for you.

So how do I construct the method call so that I can use parentheses?
 
J

Justin Collins

Joshua said:
So how do I construct the method call so that I can use parentheses?

There is really no reason to do so in most cases, but you can if you
really want:

responder = Responder.new(self)
responder.html
l = lambda { render :xml => @posts}
responder.xml(&l)
responder.respond

The "&" (in this case) says "make this the block that gets passed to the
method."

I believe you could also do:

responder.xml(&lambda { render :xml => @posts } )
or
responder.xml(&Proc.new { render :xml => @posts } )

But, again, this is not the normal way to do it. You can sort of tell,
because it is much more awkward than

responder.xml { render :xml => @posts }


Hope that helps some.

-Justin
 
A

Andrew Stewart

However, there are still
somethings I don't understand. Why can't I do this?

responder.xml( { render :xml => @posts} )

The syntax for a new hash and the syntax for a block can look the
same -- { ... } -- so you have to help Ruby's interpreter deduce
which one you want.

Your line above says send the :xml message (i.e. call the xml method)
with a new hash as the argument. (Assuming that the result of render
:)xml => @posts) can be constructed as a hash.)

It doesn't work because the method that handles this call,
method_missing, doesn't want a hash. It wants a block. So you need
to write this instead:

responder.xml() { render :xml => @ posts }

Or this:

responder.xml { render :xml => @posts }

Or this:

responder.xml do
render :xml => @posts
end

etc.

How do we know Responder's method_missing wants a block? From its
signature:

def method_missing(symbol, &block)

The way method_missing works in Ruby, the symbol argument is set to
the symbolised name of the method that's missing. In your case, this
is :xml. The only other argument specified is &block, so that's what
the method_missing needs.

Second, from this line here:
http://dev.rubyonrails.org/browser/trunk/actionpack/lib/
action_controller/mime_responds.rb#L102

Can you help me understand this line:
block ||= lambda { |responder| types.each { |type| responder.send
(type)
} }

It looks like it would be overwriting the "block" parameter of the
"respond_to" method -- and yet apparently it is not?

The ||= syntax is equivalent to += and friends. In the same way that :-

this: x += 1
is shorthand for: x = (x + 1)

You can see that :-

this: block ||= ...
is shorthand for: block = (block || ... )

So it means set block to ... unless it's already set to something.
In your situation respond_to is called with a block, so the line
doesn't change anything. The line is there to set the block variable
if respond_to is called with an array instead of a block.

Regards,
Andy Stewart
 
A

Andrew Timberlake

Why is this:
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml =3D> @posts }
end

Any better than this:

responder =3D Responder.new(self)
responder.html
responder.xml { self.render( :xml =3D> @posts) }
responder.respond

As best I can tell the accomplish the very same thing...?

-Josh


Because the first one leaves Rails to deal with the responder and in the =
second, you're taking control.

Andrew Timberlake
(e-mail address removed)
082 415 8283
skype: andrewtimberlake

"I have never let my schooling interfere with my education."
--Mark Twain
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

if the variable is set. 5
Font selector using radio button 2
do-end-blocks 5
Uhhhhh, What can I do next? 6
Command Line Arguments 0
Validates is exists 4
NoMethodError 0
exemple sur le cinéma (rails) 0

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,049
Latest member
Allen00Reed

Latest Threads

Top