Ruby Examples > Blocks

#---Blocks---

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)

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

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’ll call greet_all and pass-in a block.

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.

Here’s a detailed example:

def transform(names)
  res = names.map { |name| name.upcase }
  if block_given?
    res = res.map { |name| yield(name, prefix: 'HI') }
  end
  res
end
names = ["Brutha", "Vorbis", "Didactylos", "Nhumrod"]

Calling transform without a block:

res = transform(names)
p res
## ["BRUTHA", "VORBIS", "DIDACTYLOS", "NHUMROD"]

Calling transform with a block:

res = transform(names) do |name, prefix:, exclaim: true|
  fail "Prefix is mandatory" unless prefix
  "#{prefix} #{name}#{'!' if exclaim}"
end
p res
## ["HI BRUTHA!", "HI VORBIS!", "HI DIDACTYLOS!",
## "HI NHUMROD!"]

Calling transform with a block, but this time with a different transformation logic:

res = transform(names) do |name, prefix:|
  "#{prefix} #{name}"
end
p res
## ["HI BRUTHA", "HI VORBIS", "HI DIDACTYLOS",
## "HI NHUMROD"]

When to define a method with block support?

As you can see in these 3 examples, the purpose of passing a block (or more generally a function) is that the caller gets to define some aspect of the outcome.

More about blocks

  • If you define a function that uses yield, but call it without passing a block, Ruby won’t raise an error. Passing a block is optional. But you can check if a block is passed with the block_given? method
  • 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 only call it. You can’t call anything on it.

Next example: Procs and Lambdas.