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
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_request
This 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.