Using Blocks in Ruby
Today I was fortunate enough to be able to spend some time learning about the intricacies of blocks in Ruby. I am hoping to ramp up from basic to more complex ideas around blocks in this post so that all skill levels can benefit.
Blocks at first seem to be rather magical, and as a result, can be intimidating to dive into. So take a deep breath if you are feeling similar right now and believe that you will learn something new about blocks!
So if this is your first dive into blocks let’s point one out.
["Mon", "Tues", "Wednes"].each do |prefix|
puts "#{prefix}day"
end
The block in this chunk of code starts with do
and ends with end
. You’ve
likely seen this with curly braces as well ({ |prefix| puts "#{prefix}day" }
).
What’s happening is that each item in the array is being passed into the block
and the code inside is being executed. Let’s make up our own method to see how we
would create and use our own block.
def add_day
yield "day"
end
add_day { |word| puts "Mon#{word}" }
add_day { |word| puts "What a wonderful #{word}" }
So there are a couple of wonderful things here. First is the keyword yield
.
This keyword knows that an implicit block will be created and that it needs to
give whatever it’s provided to that block. In the above example we yield
the
string "day"
to the block and use it to puts
"Monday"
for example.
Second is the term “implicit block”. To better understand implicit, let’s look
at the same example using an explicit block.
def add_day &block
block.call "day"
end
add_day { |word| puts "Mon#{word}" }
add_day { |word| puts "What a wonderful #{word}" }
When ran, this produces the same result. The difference is that when you use
the explicit &block
syntax, a Proc
object is being created for you so that
it will be able to respond to the call
method with the argument "day"
.
Some people will want you to understand this because there are performance
trade-offs when using explicit or implicit blocks.
Let’s move towards something a little more real world. Perhaps we have a need to take a collection of numbers and we wanted to be able to do work on only those odd numbers. We don’t know what the work would be, we just want that work to only be with odd numbers values. We could write the following method to help us out.
class Array
def each_odd_number
self.each do |value|
if value.respond_to?(:odd?) && value.odd?
yield value
end
end
return self
end
end
[0,1,2,3,"foo",5,"bar"].each_odd_number do |odd_number|
puts odd_number
end
We define the each_odd_number
in the Array
class so that every instance of
Array
can now call it. We can make the logic as complex as we need it to be
and can use blocks to create new logic using the method. You can also see that
we call return self
at the end of the method. we haven’t made the
method “destructive” and it’s common convention to return the original array
when it’s not. If we didn’t do this we would have nil
returned. Let’s see
the same thing using the explicit block again.
class Array
def each_odd_number &some_block
self.each do |value|
if value.respond_to?(:odd?) && value.odd?
some_block.call value
end
end
return self
end
end
[0,1,2,3,"foo",5,"bar"].each_odd_number do |odd_number|
puts odd_number
end
Notice how I use &some_block
as an argument for the method? It’s common to
see this as &block
or sometimes as &b
in some projects, but it’s really
just a name to identify the Proc
object that’s being created and it can be
whatever you want it to be. Other than that, it’s identical to the implicit
syntax. Cool right!?
Let’s keep going!
What if we didn’t want that block to run right away? What if we want it to be “saved” so we can call it again and again whenever we want? We can use instance variables to save it and call the block whenever we want.
class Robot
def self.load_speech &block
@speech = block
end
end
We would then call load_speech
like this
Robot.load_speech do
#whatever you want could go into here
puts "Greetings."
puts "You can call me Chip"
end
We now have the speech saved to the Robot
singleton. Every time we call
load_speech
and give it a block, it changes what the @speech
variable is.
Let’s create a way for the given block to be executed with a give_speech
class method.
class Robot
def self.load_speech &block
@speech = block
end
def self.give_speech
@speech.call
end
end
Then when we call Robot.give_speech
the block given to load_speech
is
executed!
This is the beginnings of a new DSL for interacting with the Robot object. It’s basic but hopefully you are beginning to imagine some of the ways that blocks can be used.
When I saw this I started wondering about some of the other ways I’ve seen
blocks implemented in ruby projects. Let’s configure the robot before we start
interacting with it this time around. We will start be defining the DSL that
we want to be able to use to configure our Robot
. After, we will define how
we want to interact with our robot object and those configurations.
Robot.configure do |config|
config.name = "Chip"
config.set_greeting do |name|
puts "Greetings."
puts "you can call me #{name}."
end
end
Robot.name
Robot.say_greeting
So in looking at this code we want to be able to configure what the name of the robot is and how to create a greeting for the robot. Let’s see how we could implement this.
class Robot
def self.configure
yield config
end
def self.config
@config ||= RobotConfig.new
end
end
class RobotConfig
def name=(name)
@name = name
end
def set_greeting &block
@greeting = block
end
end
Here we created an instance of RobotConfig
that we memoize when the config
method is called. That object is yielded implicitly when Robot.configure
is
called with a block. The RobotConfig
object has a setter method for name and
a set_greeting
method that stores a Proc
in @greeting
.
Next we want to start describing the name
and say_greeting
methods.
class Robot
def self.configure
yield config
end
def self.config
@config ||= RobotConfig.new
end
def self.name
puts config.name
end
def self.say_greeting
config.greeting.call config.name
end
end
class RobotConfig
attr_accessor :name
def set_greeting &block
@greeting = block
end
def greeting
@greeting
end
end
I updated the name
method on the RobotConfig
object to be an attr_accessor
and then I am able to
pass it into the call
method inside say_greeting
so that it
is yielded correctly to the block that was defined in the configure
method.
Hopefully through these examples blocks will be less intimidating to work with. I found that as a learned more about how to use blocks, the less magical frameworks and gems became.