Reputation: 195
I'm wondering why the following tag methods produce different results:
Method 1:
def tag(html)
print "<#{html}>#{yield}</#{html}>"
end
Method 2:
def tag(html)
print "<#{html}>"
print yield
print "</#{html}>"
end
When I ran the following code in terminal using the above methods:
tag(:ul) do
tag(:li) { "It sparkles!" }
tag(:li) { "It shines!" }
tag(:li) { "It mesmerizes!" }
end
The first one gave me:
<li>It sparkles!</li><li>It shines!</li><li>It mesmerizes!</li><ul></ul>
The second one gave me:
<ul><li>It sparkles!</li><li>It shines!</li><li>It mesmerizes!</li></ul>
The second one is the output that I'm looking.
How come the first method prints 'yield' before it prints what comes before 'yield' in the string?
Upvotes: 1
Views: 187
Reputation: 230346
Just to echo @tadman's answer: order of evaluation AND inconsistency of api. Your block sometimes returns strings and sometimes prints strings as a side-effect.
print "<#{html}>"
print yield
print "</#{html}>"
Here you print, then yield. If the block returns a string (one of :li
blocks), then it's printed right here. If it's a :ul
block, then its side-effects happen (printing of li blocks) and nil is printed after that.
In the other case
print "<#{html}>#{yield}</#{html}>"
Ruby has to assemble one string to print. Which means yielding before any printing. Which means that side-effects happen before printing the opening <ul>
. As the ul block returns nil, that's why it's printed empty at the end of the string (<ul></ul>
).
Does it make sense?
Upvotes: 1
Reputation: 211590
The main problem is the order of operations. When you call print
it will print immediately, there's no delay, which can cause problems.
In Ruby it's often easier to deal with code that returns strings rather than code that causes side-effects like printing. If they return strings you have control over where that output goes. If they print things immediately you need to be very careful about the order you call them in.
The way you're calling that code in the final assembly with the tag(:ul)
call is actually going to be trouble. The second version of your method coincidentally orders things correctly.
It's not necessarily easy to fix this. If you return a string, then only the last string from your three tag
calls will be used. If you print, you'll have to be sure you're using the second method to make it work.
Within the Rails system there's a way of capturing the output of these things for buffering purposes, but that's a very messy thing to try and do, it can get really confused when you try and handle all cases.
Where possible create some kind of buffer these things can write to, then when everything's done write that out with print
or whatever.
Upvotes: 1