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'
endfoo 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
=> nilBut 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
=> nilNote 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
=> nilYou 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 + '!');
});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'
endThe 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
=> nilIt will work equally well when a block is not attached:
[5] pry(main)> bar
bar 1
foo 1
foo 4
bar 2
=> nilYou 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
endNow 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 + '!';
});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}!"
endAnd 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: 2However, 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}"
endIt does something very different when we run it:
[6] pry(main)> qux
qux 1
foo 1
foo 2
hello world!
=> 1When 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.