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

Discussion in 'Ruby' started by Joshua Beall, Jan 17, 2008.

  1. Joshua Beall

    Joshua Beall Guest

    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?
    --
    Posted via http://www.ruby-forum.com/.
     
    Joshua Beall, Jan 17, 2008
    #1
    1. Advertising

  2. On 17 Jan 2008, at 16:21, Joshua Beall wrote:
    > 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

    -------
    http://airbladesoftware.com
     
    Andrew Stewart, Jan 17, 2008
    #2
    1. Advertising

  3. > 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

    082 415 8283
    skype: andrewtimberlake

    "I have never let my schooling interfere with my education."
    --Mark Twain
     
    Andrew Timberlake, Jan 17, 2008
    #3
  4. Joshua Beall

    Joshua Beall Guest

    Re: Ruby syntax in "respond_to do |format| line -- can clari

    Andrew Stewart wrote:
    > 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?
    --
    Posted via http://www.ruby-forum.com/.
     
    Joshua Beall, Jan 17, 2008
    #4
  5. Re: Ruby syntax in "respond_to do |format| line -- can clari

    Joshua Beall wrote:
    > Andrew Stewart wrote:
    >
    >> 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.
    >

    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
     
    Justin Collins, Jan 17, 2008
    #5
  6. Joshua Beall

    Todd Benson Guest

    On Jan 17, 2008 11:34 AM, Andrew Timberlake <> wrote:

    > 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
    >
    > 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
     
    Todd Benson, Jan 18, 2008
    #6
  7. Joshua Beall

    Joshua Beall Guest

    Re: Ruby syntax in "respond_to do |format| line -- can clari

    Justin Collins wrote:
    > Joshua Beall wrote:
    >>> ...and you imagine executing it as a method, the block variable,

    >> format.html # index.html.erb
    >> And it should work. And indeed it does. However, there are still
    >> 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.


    So how do I construct the method call so that I can use parentheses?
    --
    Posted via http://www.ruby-forum.com/.
     
    Joshua Beall, Jan 18, 2008
    #7
  8. Re: Ruby syntax in "respond_to do |format| line -- can clari

    Joshua Beall wrote:
    > Justin Collins wrote:
    >
    >> Joshua Beall wrote:
    >>
    >>>> ...and you imagine executing it as a method, the block variable,
    >>>>
    >>> format.html # index.html.erb
    >>> And it should work. And indeed it does. However, there are still
    >>> 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.
    >>

    >
    > 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
     
    Justin Collins, Jan 18, 2008
    #8
  9. Re: Ruby syntax in "respond_to do |format| line -- can clari

    On 17 Jan 2008, at 20:11, Joshua Beall wrote:
    > 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

    -------
    http://airbladesoftware.com
     
    Andrew Stewart, Jan 18, 2008
    #9
  10. Re: Ruby syntax in "respond_to do |format| line -- can clari

    > 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

    082 415 8283
    skype: andrewtimberlake

    "I have never let my schooling interfere with my education."
    --Mark Twain
     
    Andrew Timberlake, Jan 19, 2008
    #10
  11. Re: Ruby syntax in "respond_to do |format| line -- can clari

    On 18 Jan 2008, at 17:23, Joshua Beall wrote:
    > Ok, I follow all that now. Thanks for the clarification!


    You're welcome!

    Blocks are a wonderful feature of Ruby.

    Regards,
    Andy Stewart

    -------
    http://airbladesoftware.com
     
    Andrew Stewart, Jan 19, 2008
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Nemisis

    DAL or BOL? Can someone clarify?

    Nemisis, Sep 20, 2006, in forum: ASP .Net
    Replies:
    2
    Views:
    2,112
    Nemisis
    Sep 20, 2006
  2. dos-man 64
    Replies:
    27
    Views:
    791
    Arne Vajhøj
    May 17, 2009
  3. Jamis Buck

    OpenStruct respond_to? problem

    Jamis Buck, Aug 24, 2004, in forum: Ruby
    Replies:
    17
    Views:
    270
    Mauricio Fernández
    Aug 25, 2004
  4. Trans
    Replies:
    24
    Views:
    333
    David A. Black
    Oct 23, 2007
  5. Replies:
    0
    Views:
    588
Loading...

Share This Page