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 + '!');
});
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
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 + '!';
});
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.