#---Block---
In most languages, you can pass a function as argument
to another function.
In Ruby, you can do that via a
block
which is just an anonymous function.
Before seeing how to define a function that
takes a block as an argument, and how to call
such a block within the function,
let’s do the same in Javascript first.
It will be easier to understand.
In Javascript, here’s how you define a
function that takes another function as
one of its arguments:
(You can run this by copy pasting the code into
the javascript console of developer tools in your
browser)
=begin
## <<Beginning of Javascript code>>
function GreetAll(names, fnGreeter) {
return names.map(fnGreeter)
}
With a list of names like this:
let names = ["Brutha", "Vorbis", "Didactylos", "Nhumrod"]
Here’s how GreetAll
will be called:
let result = GreetAll(names, function(name) {
let hi = (name == "Vorbis") ? "HI" : "hi"
return `${hi} ${name}`
})
console.log(result)
## ["hi Brutha","HI Vorbis","hi Didactylos","hi Nhumrod"]
## <<End of Javascript code>>
(=begin
and =end
are one way to write
multi-line comments in Ruby.)
=end
Here’s what GreetAll
does:
The passed-in function fnGreeter
is called with each
name in names
, and the results are all collected in
a new array and returned.
Now let’s do it in Ruby.
A block
passed to a function can be called within
the function using the yield
keyword.
def greet_all(names)
names.map { |name| yield(name) }
end
And here’s how you’d call greet_all
by passing in
a block in addition to a list of names.
names = ["Brutha", "Vorbis", "Didactylos", "Nhumrod"]
result = greet_all(names) do |name|
hi = (name == 'Vorbis') ? 'HI' : 'hi'
"#{hi} #{name}"
end
p result
## ["hi Brutha","HI Vorbis","hi Didactylos","hi Nhumrod"]
As mentioned in the beginning, a block
is just an
anonymous function. All the general properties of a
function applies to it. Such as:
The format_user
function returns a formatted string
of a user.
def format_user(name, age)
info = {name: name, age: age}
if block_given?
yield(info)
else
"User: #{name}, Age: #{age}"
end
end
If you call it with just the user details, the function will use the default formatter:
name, age = 'Centurion', 100
p format_user(name, age)
## => "User: Centurion, Age: 100"
But if you call it with a block that defines a format, then it will use that formatter:
result = format_user(name, age) do |user|
"#{user[:name]}'s age is #{user[:age]}"
end
p result
## => "Centurion's age is 100"
There are 2:
do end
thing is used when you have multiple
lines of code in the block.{...}
thing is used for single line blocks.The underlying question is: “Why to define a function that takes another function as an argument?”
As you can see in these examples, the purpose of passing a block (or more generally a function) is that the caller gets to define some aspect of the outcome.
But there’s a more compelling reason to use blocks.
It’s what makes Ruby special. Blocks allow you to
write DSLs. Domain Specific Languages.
DSLs are executable ruby code that looks and reads more
like domain-specific words rather than regular ruby
code thereby allowing stakeholders who are not
programmers to write code.
DSLs are pervasive in Ruby. All popular Ruby libraries define and use them. RSpec is one such. It’s a testing framework that allows you to write tests in a way that reads like English:
## If this example fails, you might need to install
## the `rspec` gem. Run this command in your terminal:
## `gem install rspec`
require 'rspec'
RSpec.describe "truth" do
describe true do
it "is truthy" do
expect(true).to be_truthy
end
it "is not falsy" do
expect(true).not_to be_falsy
end
end
describe false do
it "is falsy" do
expect(false).to be_falsy
end
it "is truthy" do
expect(false).not_to be_truthy
end
end
end
Where are the typical class, module, method
definitions?? Is expect the only method call here?
Looking closer, you’ll see that describe, it, to
, not_to, and even be_truthy and be_falsy are
all method calls. And describe and it take a block
as an argument. But the ruby syntax and blocks make it
looks like a testing language.
Another example:
Ruby’s blocks, together with metaprogramming concepts
like instance_eval allow you
to write intuitive code such as this:
## `gem install nokogiri`
require 'nokogiri'
builder = Nokogiri::XML::Builder.new
builder.root {
builder.products {
builder.widget {
builder.id_ "10"
builder.name "Awesome widget"
}
}
}
puts builder.to_xml
## <?xml version="1.0"?>
## <root>
## <products>
## <widget>
## <id>10</id>
## <name>Awesome widget</name>
## </widget>
## </products>
## </root>
def hi
if block_given?
yield
else
"No block given"
end
end