#---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.

Explaining Ruby Blocks via Javascript

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 last line is the return value of the block
  • You can define it to take multipe arguments
  • If you want to pass a block that takes keyword arguments, optional arguments etc, you can do so.

Another block example

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"

Syntax to write a Block

There are 2:

  • The do end thing is used when you have multiple lines of code in the block.
  • The {...} thing is used for single line blocks.

Why to use 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.

Domain Specific Languages

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>

More about Blocks

  • If you define a function that uses yield, but call it without passing a block, Ruby won’t raise an error. But you can check if a block is passed by calling block_given?
def hi
  if block_given?
    yield
  else
    "No block given"
  end
end
  • You can only pass at most one block to a function. If you want to pass in more functions as arguments, you must use lambdas or procs
  • It’s one of the few things in Ruby that’s not an object. You can’t call any method on it.