DEV Community

Cover image for How to take advantage of Ruby blocks

Posted on

How to take advantage of Ruby blocks

Blocks in ruby are a very powerful aspect of the language. They are often less used in when compared to other features of the language and it is something not everyone is comfortable using them, and much less writing code that consumes blocks. So let's start with a small introduction:

What are blocks?
Essentially, a method with no name. It is a piece of code, delimited either by { } or by do... end. That we are able to call in a certain context For example:

# The following syntaxes are equivalent. They will both execute the block twice.

2.times do
  puts "foo"

2.times { puts "foo" }

A more complicated example: Let's assume that for a user masquerading gem, you want a method in which you can pass in a user, grant them all permissions, run some code with those permissions in place, and then reset the old permissions once the code has finished running. This is where blocks shine. A very simple test suite for this would be something like:

RSpec.describe UserImpersonator do
  it "grants permissions while inside the block" do

    UserImpersonator.grant_all_permissions valid_user do
      expect(valid_user.roles[:admin]).to be_true

    expect(valid_user.roles[:admin]).to be_false

We first need a UserImpersonator class and a grant_all_permissions method that accepts the user, and the block of code you want to run while you have all permissions in place.

class UserImpersonator
  def self.grant_all_permissions(user, &block)

  def initialize(user)
    @user = user

  def run(&block)

Let's break up the code above:

The &block on the self.grant_all_permissions method definition lets the method know that it can expect to be passed in a block. This will instantiate a new UserImpersonator object and call the run method on it, forwarding the &block argument.

The run method's first two lines after the begin keyword are the set up, we want to cache the old permissions so that we can reset them later and assign the new ones. After this, we want to actually run the block that was passed in, and after that, we want to reset the permissions back, regardless of whether or not an error was raised somewhere in between, hence the ensure keyword.

There you have it, we've just created a method that accepts a block and runs it wherever we want, this is certainly useful, but we can go deeper

Let's try now passing a specific context into our block, or block variables, if you have used rails, even for a bit, then you have probably seen this when you iterate through your database records, for example:

User.all.each do |user|
  user.update admin: false

The block variable is what is between the pipes, user in this case.

New example: Let's imagine we're running a restaurant, and we would like to calculate the cost of an order. Since an order can be made out of different items, we want to be able to add as much or as little items within the context of that order. Again, starting with the tests, we could have an expectation like:

RSpec.describe Order do
  it "allows to make operations" do
    result = Order.cost do |c|

    expect(result).to eq 44

To make the test above pass, one possible implementation that would make the test above to pass would be:

class Order
  def self.cost

  class Actions
    def initialize
      @cost = 0

    def add_taco
      @cost += 10

    def add_guacamole
      @cost += 6

    def add_beer
      @cost += 18

Things to note:

1) The block variable, or c in the case of the test is an instance of the Actions class, which means it has access to the add_taco, add_guacamole and add_beer methods

2) You don't need to specify &block as an parameter in the cost method since you are using yield inside of the method

3) yield makes a block an OPTIONAL parameter, which means that you COULD call cost and pass no block at all. This however, will fail and complain with a

LocalJumpError (no block given (yield))

To solve this, a handy little method called block_given? allows you to verify if a block was passed in. So you could refactor the cost method to handle that:

def self.cost
  yield if block_given?

I really hope that this deep dive into the more fun and out there features of ruby was useful. Personally, I find it enlightening to find out how the inner workings of the tools I use very often within RSpec or factory_bot fit together step by step

Top comments (3)

philnash profile image
Phil Nash

Love the article and the examples. The UserImpersonator is such a perfect example of using the language to your advantage.

mickeytgl profile image

Thanks! I'm really glad you found it useful :)

cescquintero profile image
Francisco Quintero 🇨🇴

I'm currently trying to understand(more) how blocks work in order to identify when they be useful for me.

The UserImpersonator example is a step forward in this journey I'm making.

Thanks for sharing!