Dear Warren,
It's simpler than it looks. Trust me!
When I pass a block to respond_to it just "register" each renderer for
later rendering.
So, when I say:
format.xml { render :xml => @posts }
The block is not run imediatly. It's just "saved" for later.
Can you understand the code bellow?
I did this humble piece of code just for you to see clearly why they
are not run in sequence (as this is your main doubt).
#!/usr/bin/env ruby
class ExplainRespondTo
def request(render_format)
@render_format = render_format
index
end
def index
@posts = ["1st post", "2nd post"]
respond_to do |format|
format.html { render :html_tag => @posts } # I've put a "tag" here ...
format.xml { render :xml_tag => @posts } # ...Just to not
misundertand with the format.xml
end
end
def respond_to
renderer = Renderer.new
yield renderer
# When I yield render to the block received by respond_to
# format (block variable) will be pointing to the renderer
# Now that I have yield that block on this, I call #render
# and #render will actually "render" (or actually choose how to render)
renderer.actually_render(@render_format)
end
def render(options)
format, contents = options.first
# Here I could delegate the actual rendering to specific class
# based on the format requested
# in this example, I just tag the content
contents.each do |content|
puts "<#{format}>#{content}</#{format}>"
end
end
end
class Renderer
attr_reader :renderers
def initialize
@renderers = Hash.new
end
# This will be called as format.html inside respond_to block
def html(&block)
# Look, how we can save the block for later usage
@renderers[:html] = block
end
# This will be called as format.xml inside respond_to block
def xml(&block)
@renderers[:xml] = block
end
def actually_render(render_format)
# This should return the block saved with format.html or format.xml
renderer_proc = @renderers[render_format]
# Now we just call it
renderer_proc.call
end
end
e = ExplainRespondTo.new
e.request(:html)
e.request(:xml)
e.request(:json) # This is not defined in our example. So, error!
Abinoam Jr.
···
On Fri, Jan 3, 2014 at 7:06 PM, Warren Zhang <lists@ruby-forum.com> wrote:
Hi, Ryan,
Thank you very much for your quick reply. I still have problem to
understand though.
Post the code snippet again here:
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
My understanding is: respond_to method will take this block as argument,
within respond_to method, it will call this block code, the
implementation of respond_to is:
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
Notice here "block.call(responder)", I believe this "responder" is the
"format" parameter used in
do |format|
format.html # index.html.erb
format.xml { render :xml => @posts }
end
But this doesn't explain only one of format.html or format.xml is
executed. No matter what object is passed into this block as parameter
for "format", these two statement will both be executed. Unless, ruby
implements something like:
do |format|
if format == ".html"
format.html
if format == ".xml"
format.xml {render :xml => @posts }
According to what you said: the respond_to method yields
a format object to the block. Once inside the block, the code tells the
format object to perform the default action (render the HTML template)
if the request wants an HTML response. Also, it tells the format object
that, if the request wants an XML response, to execute the given lambda.
I don't see how the code "tells" the format object to perform the
default action or XML action.
--
Posted via http://www.ruby-forum.com/\.