My initial plan for this post was to write about the Ruby module Enumerable, but I realized that it would be a good idea to introduce blocks first. We’re going to need to have a good understanding of what blocks are before we talk about Enumerable.
Blocks are a concept that often are difficult for newcomers to Ruby to grasp. They are similar to anonymous functions, lambdas etc. In essence, they are a block of code that you can pass around and execute.
One way to create a code block like that is to create a Proc object.
code_block = proc do |name|
puts "Hello #{name}!"
end
code_block.call("Alex")
# outputs: "Hello Alex!"
Binding code to a variable like this is useful, since it enables us to pass behavior as a parameter to a method. One such example is file manipulation; we can pass a proc object to specify the code that we want to run while the file is open. However, we rarely want to use the code more than once, so we can omit the assignment and inline the code directly in the method invocation. This will look similar to anonymous functions in JavaScript.
open("file.txt", proc do
# do the work
end)
This doesn’t look very much like Ruby and, fortunately, there is native support in Ruby for a much prettier construct. We ignore the “proc” keyword and we add the block after the parameters instead.
open("file.txt") do
# do the work.
end
That looks a lot better and we got rid of that trailing parenthesis.
Do and end are not the only way to define blocks, you can use curly braces as well. The common convention is to use do and end on multi-line blocks and curly braces on single line code. There is however the “Weirich Convention” where you use curly braces for code where the main purpose is to return a value and do/end to signal that the code’s primary purpose is side-effects.
We can also pass parameters to blocks and we define them within pipes like this:
fruits = ["apple", "banana", "orange"]
fruits.each do |name|
puts name
end
# outputs:
# apple
# banana
# orange
To use blocks in your own method you use the keyword “yield”.
def foo
puts "yielding to the block"
yield
puts "done with the block"
end
foo { puts "This is the code in the block" }
# outputs:
# yielding to the block
# This is the code in the block
# done with the block
If you want to send parameters to the block you just use them as parameters in the yield call. In the following example, we yield names to the block and the block prints a greeting.
def people
yield "Alex"
yield "Beth"
yield "Carl"
end
people do |name|
puts "Nice to meet you #{name}!"
end
# outputs:
# Nice to meet you Alex!
# Nice to meet you Beth!
# Nice to meet you Carl!
I hope that this explanation will be useful and serve as a foundation to the things I will talk about in my next post.