Simplify your Code by using Ruby's blocks.

January 18, 2017

Image 11

This week my writing topic is on Ruby’ blocks and how as a developer we can use them to simplify our code. This is part of my on-going research project where I examine the code of an open source project and discuss something of interest.

My current focus is on the Faraday Gem - a popular project that provides a simple configurable interface for HTTP requests. The project heavily uses blocks , which is something that I rarely use in my own code. With that as a motivation, I have outline how Faraday uses configuration blocks and how that benefits the project.

Overview of a block

There are a number of really good blog post that do a great job at discussing what blocks are. Take a look at ruby blocks in less than 5 minutes for a good overview. The key concepts for us is that the code blocks executes when the program reaches a yield statement. The other useful thing about blocks is that they capture their context at the time of creation. This means that only those local variables that are in scope when the block is created are available when the block is executed.

The example below, the local variable a exists both:

  • within the function some_function and
    • outside the function.

When the code runs, and the variable is printed you would expect ** "this lives within the some_function" , but instead you get ** "this exists when the block is created”. That is because variable a exists in different scopes, and the scope the block uses is assigned when it is first created rather than when it is executed.

def some_function
  a = "this lives within the some_function"
  yield if block_given?
end

a = "this exists when the block is created"

some_function do
  puts a
end

The code outputs  this exists when the block is created.

How Faraday Gem uses blocks

The below code is a typical example of how Faraday uses blocks to configure the methods behaviour. The method run_request calls a block on another method called build_requestThis configures the req object using closure to pass in any parameters such as url, headers or body.

What is really interesting here is having another yield within a block. This allows the method run_request to receive a block that is then pass to build_request method to configure its req field.

def run_request(method, url, body, headers)
# code has been removed 
 request = build_request(method) do |req|
req.url(url)                if url
req.headers.update(headers) if headers
req.body = body             if body
yield(req) if block_given?
  end
# code has been removed
end

Using blocks to simplify the code.

A method will often require slightly different behaviour for one of its invocation. Maybe it needs to print out a log message or send an email but that is restricted to a single method call.

The developer is faced with a quandary in what to do.

They could either:

  • Create a new method with the modified behaviour.
  • Modify the existing method with a switch to execute the new behaviour. The behaviour is switched on using a parameter that is invoked with the method. `
  • Allow a block to configure a method to modify how the method is executed.

While the first option gets the job done it can result in a number of methods that essentially do the same thing. It also means that the code is often repeated which adds to the programme overhead, and any future modifications are required in multiple places.

The second option using parameters to distinguish between the options can become long and unwieldy. In this situation it is very difficult not to end up in a mess of switch statements. This can make future modification difficult as each logic path has to be checked to ensure that it hasn’t unwittingly being changed.

The alternative is to just change the behaviour of that one method being invoked using a configuration block. This could work as follows, by having a method yield itself.

class SomeFancyObject
   def some_method
     # does its thing
    yield(self) if block_given?
   end
end

This allows any invocations to be customised by allowing blocks to be passed to the method. The yield includes itself as a parameter that gives the block access to the instance methods.

This can remove the need to modify the existing method - see the example below. When the method is executed, the block configures that the instance method does_another_thing also runs

some_fancy_object = SomeFancyObject.new

some_fancy_object.some_method do |a|
 a.does_another_thing
end

The Gem library Rack uses a instance_eval to change the selfto that of the instance. This removes the need to include the parameter self in the block which does have a cleaner look, though you now have the downside risk of confusing future maintainers with a block that no longer works as expected.

Example using instance_eval

class SomeFancyObject
   def does_another_thing 
    puts ‘does_another_thing’
end

   def some_method(&block)
     # does its thing
    instance_eval(&block) if block_given?
   end
end

some_fancy_object = SomeFancyObject.new

some_fancy_object.some_method do |a|
    does_another_thing
end

Using blocks to configure a method can be a great way to extend behaviour and one that I will be using more often in the future.

Appendix