Today I had to silence some output streams so that messages don’t pollute the STDOUT. I used the silence_stream method built into Rails, described well in this article.
# ...my code...
end
Very nice, except I also wanted to filter STDERR output. Not a problem.
# ...my code...
end
Well, shit. Here I got a “too many arguments” error. So it doesn’t support multiple streams. I guess it wants me to be ugly.
silence_stream(STDERR) do
# ...my code...
end
end
I personally don’t take kindly this level of suckiness around here. I want example #2 to work. Well, after lighting some candles and praying to the Ruby gods, I came up with a scary implementation. In fact it’s so spooky that it may have the same effect as glancing at the Mexican Starting Frog of Southern Sri Lanka. You may then throw a chair at me on the Jerry Springer show, so I rather not show it to you.
However, after having cleansed my .rb file with some broc flowers and xander roots I came up with a better solution. Recursion is the key. See if you can understand what is happening.
silence_stream(streams.pop) do
if streams.empty?
yield
else
silence_streams(*streams, &blk)
end
end
end
This is a basic use of recursion, which is not immediately clear when dealing with nested Ruby blocks. When multiple streams are passed in I’m popping out the last stream and silencing it alone, then calling the same method with the remaining streams. This is happening until only one stream left to silence. The last one gets silenced with the original block passed in, which means that the original block is executed at the deepest nesting level, where all passed streams are silenced. As a result I got this much desired functionality without resorting to eval, yay.
# ...my code...
end
There we go. This solution can be applied to cases where you’d rather avoid overriding stuff (thus keeping out of method implementation details).
Update: Made recursion more concise thanks to apeiros on #railsbridge.
Why not
def silence_streams(*streams, &block) streams.each {|s| silence_stream(s, &block) } end
It’s more concise, clearer at first sight, and more efficient, IMO.
Maybe I’m misunderstanding but wouldn’t this execute
silence_stream(STDOUT) { same_code; } silence_stream(STDERR) { same_code; }
do something like def silence_streams(*streams, &block) s = streams.pop streams.each {|s| silence_stream(s){} } silence_stream s, &block
end so the block doesn’t get executed twice
This won’t work because the block has to run nested within all stream-silencing blocks.
silence_stream(particular_stream) do # code for which stream is silenced end
In your case you’re silencing all streams individually, and not running any code in them. Then in the end you’re only silencing one last stream for &block passed in. Basically
dang true. my fault, thanks for your answer