Avoiding nested blocks

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.

  silence_stream(STDOUT) do
    # ...my code...
  end

Very nice, except I also wanted to filter STDERR output. Not a problem.

  silence_stream(STDOUT, STDERR) do
    # ...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(STDOUT) do
    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.

def silence_streams(*streams, &blk)
  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.

  silence_streams(STDOUT, STDERR) do
    # ...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.

5 responses to “Avoiding nested blocks”

  1. Nicolás Sanguinetti

    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.

  2. Anonymous

    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

Leave a Reply