Simplify your Code by using Ruby's blocks.
January 18, 2017
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
- 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.