5 ways of forwarding your work to some other object in Ruby

Many times in programming, we use encapsulation, we inject dependencies into our classes, we wrap other objects, and very often we build some decorators for other classes. It’s usually done to introduce a layer of abstraction, to hide implementation details behind some kind of interface or to easily switch dependencies during our tests.

As we already know, Ruby is a powerful language, sometimes even too powerful, which means that it provides many, very similar, methods for the same thing. Can you see the differences in this basic example?

Until you start using them with ActiveRecord, you can invoke them interchangeably. That’s kinda misleading.

Talking about ActiveRecord, what with preload and eager_load? What about joins and includes? Do you always remember when to use what without diving into the documentation?

The same is with delegation. You probably already use method forwarding in your code as I do. However, I can’t remember when to use what, so I’d like to summarize different approaches for a presented problem.

USE CASE

Our case study is as follows — we come into a sandwich shop and we want to buy a sandwich. Unfortunately, there’s a very lazy employee that delegates his job to a new intern.

Our intern, who is basically a sandwich maker, may look as follows:

So let’s begin our journey and try to model our sandwich shop.

1. Explicitly

This is the most basic, obvious and simple way to forward our work to some other object. We just call a method on a wrapped object from one of our own methods.

2. method_missing

We can be here even more lazy or… predictable. Of course, we can use the above solution, but what if our intern will learn a new skill? Will we have to define a new method for our employee each time? Let’s be smart and check whether our sandwich maker is possible to do a thing that we were asked for.

3. Forwardable

Module Forwardable is a part of Std-lib and it provides you delegation of specified methods to a designated object.

The def_delegators line is pretty straightforward — it states that a call to make_me_a_sandwich should be instead handled by the @sandwich_maker instance variable (note: we could have written :sandwich_maker if we’ve defined it with attr_reader)

4. Delegate

If your project includes ActiveSupport (and every Rails project does) you have a more clean and easy way to implement the delegation pattern — the Module#delegate extension. It provides delegate method that you can use in your class or your modules to delegate a specific method to an associate object.

Methods can be delegated to instance variables, class variables, or constants by providing them as a symbol. At least one method and the :to option are required.

5. SimpleDelegator

SimpleDelegator provides the means to delegate all supported method calls to the object passed into the constructor and even to change the object being delegated to at a later time

The main problem with SimpleDelegator is that it redefines a class which may be time-consuming during a debugging session.

Summary

A decorator is a design pattern. Its intent, as described by the Gang of Four is:

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Thanks to aforementioned methods you can easily wrap some objects and instruct them some methods executions.

Next time, when someone asks you to make a sandwich, you will know how to delegate the work to someone else.

Subscribe to get the latest content immediately

https://tinyletter.com/KamilLelonek

Resources

Jim Gay’s book Clean Ruby dives into an understanding delegation and explores ways to keep your projects well organized, maintainable, and loosely-coupled, giving you ways to share behavior and avoid cognitive overhead.