My Struggle with Ruby Blocks

My name is Rob and I had trouble understanding Ruby blocks.

This is my story.

FADE IN:

Oh, hey, here’s a simple example of a block in Ruby:

display_name do
  puts "Rob"
end

Wait, why not just do this:

display_name("Rob")

Oh, I see, because display_name doesn’t want to decide how to display my name. Instead, it wants me to supply the line of code that does the displaying. Three cheers for flexibility!

Let’s add some comments to illustrate this difference:

# I'm passing a block of code for display_name to run
display_name do
  puts "Rob"
end
# > Rob

# I'm passing a value for display_name to use with its own printing code
display_name("Rob")
# > Rob

But I don’t really understand how display_name runs my block of code. Maybe it’ll make more sense when I look at the method definition:

def display_name
  yield
end

Hmm. That method definition doesn’t exactly scream, “Yo, I take block of code and run it!” There’s no argument in the method signature that accepts my block, and I don’t see anything that actually calls my code either.

Hidden in plain view

After some digging, it turns out ANY Ruby method can invisibly accept a block and the yield statement illustrates the calling of the given block (at runtime my code goes right where that yield statement is).

Wow, that’s TOTALLY not obvious.

There’s an alternate–and more explicit–way of defining such a method:

def display_name(&block)
  block.call if block
end

Above, there’s an argument in the method signature that indicates that there’s a block of coding being passed in and explicitly called. There’s even a check to make sure a block was provided!

Despite its clarity, nobody uses this pattern because it’s slow and Rubyist tend to favor brevity.

OK, achievement unlocked, but the example above is too trivial. It doesn’t illustrate what blocks are good for. Let’s modify it slightly:

def display_name
  puts "I'm about to run your code..."
  yield
  puts "That felt good."
end

The method isn’t much more useful, but I kinda-sorta see where we’re going with this. The method can do all sorts of things before and after it runs my block. I’m starting to see blocks as a form of code injection.

Putting on my big boy pants

I’ve got this now. Let’s do some Rails!

@product = Product.find(params[:id])
respond_to do |format|
  format.html
  format.json { render json: @product.to_json }
end

OK. Based on what I know, I’m calling respond_to and passing it 2 lines of code to run, which are:

format.html
format.json { render json: @product.to_json }

But what is format? Where does its value come from and why am I calling methods on it?

Is format an argument to respond_to?

<< scratches head >>

No, that would look like this:

respond_to(arg) do |format|

Or as we Rubyist are accustomed to:

respond_to arg do |format|

Turns out that format is an argument to my block. Its value is passed (or yielded) to me by the method I’m calling.

Let me phrase that differently: I’m calling respond_to which does some stuff and then passes my block a value held in the variable format.

Right, but what is format? What can I do with it?

respond_to do |format|
  # Umm...
end

A minor epiphany

Here was the major realization I had about blocks: without understanding the inner-workings of the method I’m passing a block to, I can’t really write my block.

I couldn’t treat it as a black box, as badly as I wanted to. With many methods (or APIs), I can simply pass in arguments and get a return value without ever having to understand what actually goes on inside. And that encapsulation is a beautiful thing.

But with blocks, I am actively participating in writing the method I’m calling, so I have to understand the context in which my code is called. I also have to understand the objects that are yielded to my block via its arguments.

Let’s play detective:

respond_to do |format|
  # What is format, really?
  logger.debug format.class
end
# > ActionController::MimeResponds::Collector

I’m calling respond_to and passing it some lines of code to run. My block is given a Collector object to work with via the variable named format.

Now I can look at the Collector class and see what methods I can call (such as html and json). Ah-ha!

Iterators

I’d be remiss if I didn’t touch on iterators. See, in Ruby, blocks are also used quite frequently to iterate over collections:

food = %w{apple orange grape coconut}
food.each do |f|
  puts "I like to eat #{f}"
end

Armed with my knowledge about blocks, this code is super-easy to understand!

I’m calling food.each once and it does all the legwork required to loop through the array which called it (i.e., food). Every iteration within each executes my puts block, passing it one of them items from the array via the variable f.

And that’s all there is to it! :-)

Comments? Questions?

Did I miss anything? Veteran Rubyists dying to tear me to shreds? Leave a comment below.

6 Comments

  1. Thomas Eichberger

    Just a small hint:

    def display_name(&block)
    block.call if block_given?
    end

    is not really so good, because block_given? is used, if the method has no formal arguments:

    def foo
    yield if block_given?
    end

    So what you probably meant, was:

    def display_name(&block)
    block.call if block
    end

  2. I happen to be writing up a tutorial on blocks now, and this should be helpful in ensuring I provide enough detail on the difficult parts. Thanks for posting it!

  3. Hey Rob,

    One tip I’d recommend that should help for learning ruby is to use Pry in development and test. It will give you an interactive REPL (aka – console) that you can investigate the state of the code that is running. I can’t code w/out Pry these days and it will remove the need to type logger.debug statements for the majority of cases.

    To setup pry, add these to your Gemfile in the development / test sections (I don’t recommend running Pry in production):

    group :development, :test do
    gem 'pry'
    gem 'pry-debugger'
    end

    Then when you want to inspect your code you just add the following:

    binding.pry

    Happy coding!
    -Tom

Trackbacks for this post

  1. Ruby and Blocks

Leave a Reply

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax