Blocks are one of the most powerful and often overlooked feature of ruby. I must confess that it took me a while to figure out how ruby blocks work and how they can be useful in practice.
There is something about yield
that makes blocks very hard to understand at first. I’m going to talk about some of the concepts and provide a few examples so by the end of this post you’ll have a solid understanding of ruby blocks.
What you’ll be reading about in this post
- The basics: What are ruby blocks?
- How yield works
- Passing blocks to methods
- Yield takes parameters too
- What does
&block
mean? - Return value
- How does
.map(&:something)
work? - Iterators and how to build one yourself
- Initialize objects with default values using blocks
- Ruby block examples
The basics: What are ruby blocks?
A block is basically just code that you put inside do
and end
. That’s it. “But where’s the magic?” you might ask. We’ll get there in just a minute but first things first.
You can write the block in two ways:
- Multi-line, between
do
andend
, - Inline, between
{
and}
Both versions will do the exact same thing so it’s up to you which one you choose. As a general style-guide, it’s better to use the multi-line version if your code has more than one line, just to make it easier to read.
Here’s a basic example of a multi-line block:
[1, 2, 3].each do |n|
puts "Number #{n}"
end
It’s called a multi-line block because it’s not inline, not because it’s got more than one line of code (which is not the case here). The same example can be written with an inline block.
[1, 2, 3].each {|n| puts "Number #{n}"}
Both versions will print numbers 1, 2 and 3 in order. The little n
letter you see between the pipes (|n|
) is called a block parameter and it’s value in this case is going to be each of the numbers in turn, in the order they are listed inside the array. So for the first iteration, the value of n
will be 1, then for the second iteration, the value will be 2, and then 3.
Number 1
Number 2
Number 3
=> [1, 2, 3]
How yield works
Here’s the bad wolf. This guy is responsible for all the confusion and magic around ruby blocks. I think most of the confusion comes from the way it calls the block and how it’s passing parameters to it. We’ll be looking at both scenarios in this section.
def my_method
puts "reached the top"
yield
puts "reached the bottom"
end
my_method do
puts "reached yield"
end
reached the top
reached yield
reached the bottom
=> nil
So basically when the execution of my_method
reaches the line with the call to yield
, the code inside the block gets executed. Then, when the code inside the block finishes, the execution of my_method
continues.
Passing blocks to methods
A method doesn’t need to specify the block in it’s signature in order to receive a block parameter. You can just pass a block to any function but unless that function calls yield
, the block won’t get executed.
On the other hand, if you do call yield
in your method, then the block parameter becomes mandatory and the method will raise an exception if it doesn’t receive a block.
If you want to make the block an optional parameter, you can use the block_given?
method which will return either true or false depending on if a block was passed in to the method or not.
Yield takes parameters too
Any parameter passed to yield
will serve as a parameter to the block. So when the block runs, it can use the parameters passed in from the original method. Those parameters can be variables local to the method in which yield
lives in.
The order of the arguments is important because the order you use to pass in the parameters is the order in which the block receives them.
One thing to note here is that the parameters inside the block are local to the block (unlike those passed in from the method to the block).
What does &block
(ampersand parameter) mean?
You’ve probably seen this &block
all over the place in ruby code. It’s how you can pass a reference to the block (instead of a local variable) to a method. In fact, ruby allows you to pass any object to a method as if it were a block. The method will try to use the passed in object if it’s already a block but if it’s not a block it will call to_proc
on it in an attempt to convert it to a block.
Also note that the block
part (without the ampersand) is just a name for the reference, you can use whatever name you like if it makes more sense to you.
def my_method(&block)
puts block
block.call
end
my_method { puts "Hello!" }
#<Proc:0x0000010124e5a8@tmp/example.rb:6>
Hello!
As you can see in the example above, the block
variable inside my_method
is a reference to the block and it can be executed with the call
method. call
on the block is the same as using yield
, some people like to use block.call
instead of yield
for better readability.
Return value
yield
returns the last evaluated expression (from inside the block). So in other words, the value that yield
returns is the value the block returns.
def my_method
value = yield
puts "value is: #{value}"
end
my_method do
2
end
value is 2
=> nil
How does .map(&:something)
work?
You’ve probably used shortcuts like .map(&:capitalize)
a lot, especially if you’ve done any Rails coding. It’s a very clean shortcut to .map { |title| title.capitalize }
.
But how does it really work?
It turns out that the Symbol class implements the to_proc
method which will unwrap the short version into it’s longer variant. Nice eh?
Iterators and how to build one yourself
You can call yield as many times as you want inside a method. That’s basically how iterators work. Calling yield
for each of the elements in the array mimics the behavior of the built in ruby iterators.
Let’s see how we can write a method similar to the map
method in ruby.
def my_map(array)
new_array = []
for element in array
new_array.push yield element
end
new_array
end
my_map([1, 2, 3]) do |number|
number * 2
end
2
4
6
Initialize objects with default values
A cool pattern we can use with ruby blocks is to initialize an object with default values. You’ve probably seen this pattern if you’ve ever ventured into a .gemspec
file from any ruby gem.
The way it works is, you have an initializer that calls yield(self)
. In the context of the initialize
method, self
is the object being initialized.
class Car
attr_accessor :color, :doors
def initialize
yield(self)
end
end
car = Car.new do |c|
c.color = "Red"
c.doors = 4
end
puts "My car's color is #{car.color} and it's got #{car.doors} doors."
My car's color is Red and it's got 4 doors.
Ruby blocks examples
Examples are all the rage these days so let’s try to find a few interesting ways of using blocks in real world (or as close to real world as possible) scenarios.
Wrap text in html tags
Blocks are the perfect candidate whenever you need to wrap a chunk of dynamic code within some static code. So for example if you want to generate an html tag for some text. The text is the dynamic part (cause you never know what you’ll want to wrap) and the tags are the static part, they never change.
def wrap_in_h1
"<h1>#{yield}</h1>"
end
wrap_in_h1 { "Here's my heading" }
# => "<h1>Here's my heading</h1>"
wrap_in_h1 { "Ha" * 3 }
# => "<h1>HaHaHa</h1>"
Note that the power of using blocks over methods is when you need to reuse some of the behavior but do something slightly different with it. So let’s say we have a string we want to wrap inside html tags and then do something different with it.
def wrap_in_tags(tag, text)
html = "<#{tag}>#{text}</#{tag}>"
yield html
end
wrap_in_tags("title", "Hello") { |html| Mailer.send(html) }
wrap_in_tags("title", "Hello") { |html| Page.create(:body => html) }
In the first case we’re sending the <title>Hello</title>
string via email and in the second case we’re creating a Page
record. Both cases use the same method but they do different things.
Take a note
Let’s say we want to build a way to quickly store ideas into a database table. For that to work we want to pass in the note and have the method deal with the database connections. Ideally we’d like to call Note.create { "Nice day today" }
and not worry about opening and closing database connections. So let’s do this.
class Note
attr_accessor :note
def initialize(note=nil)
@note = note
puts "@note is #{@note}"
end
def self.create
self.connect
note = new(yield)
note.write
self.disconnect
end
def write
puts "Writing \"#{@note}\" to the database."
end
private
def self.connect
puts "Connecting to the database..."
end
def self.disconnect
puts "Disconnecting from the database..."
end
end
Note.create { "Foo" }
Connecting to the database...
@note is Foo
Writing "Foo" to the database.
Disconnecting from the database...
The implementation details of connecting, writing and disconnecting to and from the database were left out since they’re out of the scope of this article.
Find divisible elements of an array
It seems like I’m getting further and further away from “the real world scenario” but anyways, I’m gonna shoot one last example. So let’s say you want to get every element of an array that is divisible by 3 (or any number you choose), how would you do that with ruby blocks?
class Fixnum
def to_proc
Proc.new do |obj, *args|
obj % self == 0
end
end
end
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].select(&3)
puts numbers
3
6
9
Conclusion
You can think of blocks as simply a chunk of code, and yield
allows you to inject that code at some place into a method. That means you can have one method work in different ways, you don’t have to write multiple methods (you can reuse one method to do different things).
You’ve made it! Having read the whole post means you’re on your way to find more creative uses of ruby blocks. If for some reason you’re still confused or feel like there’s something missing from the text please let me know in the comments.