Skip to content

Ruby Blocks

In Ruby, methods group statements for reusable execution. Similarly, Ruby features Blocks, which are fundamental to its expressive power and are often used for iteration, callbacks, and managing resources.

  • A block is a chunk of code that can be associated with a method call.
  • Blocks are typically anonymous but can be converted into objects called Procs.
  • Code within a block is usually enclosed in do...end (for multi-line blocks) or curly braces {} (for single-line blocks).
  • A method invokes an associated block using the yield keyword.
  • If a block is expected, you can check for its presence using block_given?.
method_name do |parameter1, parameter2|
# statement1
# statement2
# ...
end
method_name { |parameter| statement }

This tutorial will guide you through invoking blocks using yield, passing parameters to blocks, and understanding how blocks interact with methods, including converting them to Proc objects.

The yield keyword is used within a method to transfer control to the block associated with the method call. Let’s see an example:

#!/usr/bin/env ruby
def custom_method
puts "Inside the method, before yield."
yield if block_given? # Safely yield only if a block is provided
puts "Inside the method, after yield."
yield if block_given?
end
custom_method do
puts "Inside the block!"
end
puts "--- Calling method without a block ---"
custom_method # This won't raise an error due to block_given?

This will produce the following result:

Inside the method, before yield.
Inside the block!
Inside the method, after yield.
Inside the block!
--- Calling method without a block ---
Inside the method, before yield.
Inside the method, after yield.

You can also pass arguments to the block via the yield statement. The block then defines parameters to receive these arguments:

#!/usr/bin/env ruby
def greet
yield "Alice", 25
puts "Back in the greet method."
yield "Bob", 30
end
greet do |name, age|
puts "Hello, #{name}! You are #{age} years old."
end

This will produce the following result:

Hello, Alice! You are 25 years old.
Back in the greet method.
Hello, Bob! You are 30 years old.

Here, yield "Alice", 25 passes “Alice” and 25 to the block. The block receives these values in its parameters |name, age|. If the block expects fewer arguments than yield provides, extra arguments are ignored. If it expects more, nil is assigned to the extra parameters.

Blocks and Methods: Explicit Block Parameters

Section titled “Blocks and Methods: Explicit Block Parameters”

While yield implicitly calls a passed block, methods can explicitly capture a block as a Proc object if the last parameter in the method definition is prefixed with an ampersand (&). This Proc object can then be called using its call method.

#!/usr/bin/env ruby
def execute_block(&my_proc)
if my_proc
puts "Proc received, calling it now."
my_proc.call("Explicitly called")
else
puts "No block was passed."
end
end
execute_block do |message|
puts "Inside the block: #{message}"
end
execute_block # Calling without a block

This will produce the following result:

Proc received, calling it now.
Inside the block: Explicitly called
No block was passed.

This mechanism is powerful as it allows blocks to be stored, passed around, and called later. You can also create Proc objects explicitly using Proc.new, lambda, or the stabby lambda syntax -> { }.

#!/usr/bin/env ruby
# Using Proc.new
my_proc = Proc.new { |x| puts "Proc.new called with #{x}" }
my_proc.call(10)
# Using lambda
my_lambda = lambda { |x| puts "Lambda called with #{x}" }
my_lambda.call(20)
# Using stabby lambda (preferred for lambdas)
stabby_lambda = ->(x) { puts "Stabby lambda called with #{x}" }
stabby_lambda.call(30)
# Passing a Proc to a method expecting a block
def take_proc_as_block(greeting, &block)
block.call(greeting)
end
greeter_proc = ->(name) { "Hello, #{name}!"}
take_proc_as_block("World", &greeter_proc) # The & converts Proc to block
# If take_proc_as_block used yield, you could pass greeter_proc using `yield greeter_proc`
# and then the method call would be take_proc_as_block("World") { |proc_arg| proc_arg.call("something") }

Lambdas have stricter argument checking than Procs created with Proc.new. Also, return inside a Proc.new block returns from the method where the Proc was defined, while return inside a lambda returns from the lambda itself.

Ruby allows defining BEGIN and END blocks. BEGIN blocks are executed before any other code in the script, even before parsing fully completes. END blocks are executed after the program has finished executing, including any at_exit handlers.

#!/usr/bin/env ruby
puts "This is the main program execution."
BEGIN {
puts "BEGIN block: Executed first."
}
END {
puts "END block: Executed last."
}
BEGIN {
puts "Another BEGIN block: Order matters for BEGIN blocks."
}
END {
puts "Another END block: Reverse order for END blocks."
}

A program may include multiple BEGIN and END blocks. BEGIN blocks are executed in the order they are encountered. END blocks are executed in reverse order of their appearance. This produces:

BEGIN block: Executed first.
Another BEGIN block: Order matters for BEGIN blocks.
This is the main program execution.
Another END block: Reverse order for END blocks.
END block: Executed last.

While BEGIN and END blocks are part of Ruby, their use in modern application development is rare. They are more common in standalone scripts for setup or cleanup tasks. For more complex applications, dependency management tools and framework-specific initializers/finalizers are preferred.

  • yield vs. &block: yield is simpler for direct block invocation. &block provides more flexibility by converting the block to a Proc object.
  • Proc vs. lambda: Key differences are argument checking (lambdas are strict) and return behavior.
  • block_given?: Always use this before yield if a block is optional, to prevent LocalJumpError.

For deeper understanding, explore: