Skip to content

Instantly share code, notes, and snippets.

@noonat
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save noonat/39f334619780ce400fd7 to your computer and use it in GitHub Desktop.
Save noonat/39f334619780ce400fd7 to your computer and use it in GitHub Desktop.

Understanding Blocks in Ruby

This is a quick, visual explanation of how blocks work in Ruby.

Let's define a function foo that accepts an optional block callback, and calls it if it was passed:

def foo
  puts 'foo 1'
  if block_given?
    puts 'foo 2'
    yield 'world'
    puts 'foo 3'
  end
  puts 'foo 4'
end

foo uses the yield keyword to invoke the callback, but note that it also could have specified a &block parameter, as we do in bar below, and invoked it with block.call 'foo' instead. The yield syntax is just a more terse way of invoking the callback, when you don't actually need a variable for it.

If you wrote foo in JavaScript instead, it might look like this:

function foo(callback) {
  console.log('foo 1');
  if (callback) {
    console.log('foo 2');
    callback('world');
    console.log('foo 3');
  }
  console.log('foo 4');
}

The block_given? function used in foo is a method defined on Ruby's Kernel object -- sort of like the window object in JavaScript. Any methods on Kernel are automatically available everywhere in Ruby. It returns true if a block was attached when foo is called. That is, if foo is called like so, block_given? will return true and the block will be called:

[1] pry(main)> foo do |name|
[1] pry(main)*   puts "hello #{name}!"
[1] pry(main)* end
foo 1
foo 2
hello world!
foo 3
foo 4
=> nil

But if it was instead called without a block, block_given? would return false and that section would be skipped:

[2] pry(main)> foo
foo 1
foo 4
=> nil

Note that you can also attach blocks using a brace syntax. That is, this syntax is equivalent to the do ... end syntax used earlier:

[3] pry(main)> foo { |name| puts "hello #{name}!" }
foo 1
foo 2
hello world!
foo 3
foo 4
=> nil

You should read both of these as equivalent to passing a callback function to foo. For instance, to continue the JavaScript example before, you could write similar code in JavaScript as:

foo(function(name) {
  console.log('hello ' + name + '!');
});

Passing Blocks

Now let's define a function bar which accepts a block, but only captures it as a variable. It won't actually invoke the block, but instead will pass it along when it calls foo:

def bar(&block)
  puts 'bar 1'
  foo(&block)
  puts 'bar 2'
end

The special & operator indicates to Ruby that the block variable should be assigned the block attached to this function -- the name of the variable is unimportant. The same symbol is used to pass it along to the foo function.

If you wrote bar in JavaScript instead, it might look like this:

function bar(callback) {
  console.log('bar 1');
  foo(callack);
  console.log('bar 2');
}

Let's call bar and see what happens:

[4] pry(main)> bar do |name|
[4] pry(main)*   puts "hello #{name}!"
[4] pry(main)* end
bar 1
foo 1
foo 2
hello world!
foo 3
foo 4
bar 2
=> nil

It will work equally well when a block is not attached:

[5] pry(main)> bar
bar 1
foo 1
foo 4
bar 2
=> nil

Results of Blocks

You may have noticed the => nil at the end of the all the pry executions above. That's the return value of foo and bar when we call it, which is currently just nil because the puts statements at the end of the methods has a result of nil.

But what if we wanted these functions to instead return the value of the block executed in foo? This is easily done. Let's update the functions:

def foo
  puts 'foo 1'
  if block_given?
    puts 'foo 2'
    result = yield 'world'
    puts 'foo 3'
  end
  puts 'foo 4'
  result
end

def bar(&block)
  puts 'bar 1'
  result = foo(&block)
  puts 'bar 2'
  result
end

Now let's call it, and make the last line of the block something other than a puts statement:

[5] pry(main)> bar do |name|
[5] pry(main)*   puts "hello #{name}!"
[5] pry(main)*   "goodbye #{name}!"
[5] pry(main)* end
bar 1
foo 1
foo 2
hello world!
foo 3
foo 4
bar 2
=> "goodbye world!"

As you can see, the result of the block is the last line executed in the block, just like any other expression in Ruby, and the result of the yield statement is the result of the block.

You might rewrite this in JavaScript like so:

function foo(callback) {
  var result;
  console.log('foo 1');
  if (callback) {
    console.log('foo 2');
    result = callback('world');
    console.log('foo 3');
  }
  console.log('foo 4');
  return result;
}

function bar(callback) {
  var result;
  console.log('bar 1');
  result = foo(callback);
  console.log('bar 2');
  return result;
}

bar(function(name) {
  console.log('hello ' + name + '!');
  return 'goodbye ' + name + '!';
});

Returning from a Block

Since I've been providing JavaScript equivalents of Ruby code, it might be worth pointing out a major difference between the behavior of the previous examples. In the previous Ruby example, we have:

bar do |name|
  puts "hello #{name}!"
  "goodbye #{name}!"
end

And in the JavaScript one:

bar(function(name) {
  console.log('hello ' + name + '!');
  return 'goodbye ' + name + '!';
});

A major difference here is that the JavaScript code is using a return statement to return the result from the callback. In this case, the two examples are equivalent, but the return keyword in Ruby does not have the same behavior in a block that it does in a function callback in JavaScript.

For example, if we write this in JavaScript:

function qux() {
  var result;
  console.log('qux 1');
  result = foo(function(name) {
    console.log('hello ' + name + '!');
    return 1;
  });
  console.log('qux 2: ' + result);
  result = foo(function(name) {
    console.log('hello again ' + name + '!');
    return 2;
  })
  console.log('qux 3: ' + result);
}

We will see this result when we run the function, as expected:

> qux()
qux 1
foo 1
foo 2
hello world!
foo 3
foo 4
qux 2: 1
foo 1
foo 2
hello again world!
foo 3
foo 4
qux 3: 2

However, if we write something similar using the return keyword in Ruby:

def qux
  puts "qux 1"
  result = foo do |name|
    puts "hello #{name}!"
    return 1
  end
  puts "qux 2: #{result}"
  result = foo do |name|
    puts "hello again #{name}!"
    return 2
  end
  puts "qux 3: #{result}"
end

It does something very different when we run it:

[6] pry(main)> qux
qux 1
foo 1
foo 2
hello world!
=> 1

When you return from within a block in Ruby, that return keyword doesn't apply to the block itself -- instead of applies to the function that the block is contained in. So our return actually returned from qux directly. It even skipped the rest of the code in foo, so we don't see a foo 3 or foo 4 printed!

This is why it's important to remember that the last line executed in a given Ruby block is the implicit result of the block. Only use return when you actually want to return from the function being called, not the block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment