Perusing delegate.rb from Ruby’s Standard Library
It is a rather common claim that Object Oriented programming is a lot about passing messages between objects. Also, OO encourages us to find the right nouns and verbs in order to solve a given problem. I often like to think about a program as a theatrical stage, upon which participants act and speak with each other. Sometimes, though, a character might need to say something to another character through a third participant; that go-between actor would be asked to delegate the message to the target character. Back to programming, delegation
…refers to evaluating a member (property or method) of one object (the receiver) in the context of another, original object (the sender). / Wikipedia
This definition is quite similar to the theatrical analogy — it defines delegation as the process of passing a message to an object only to have that object forward the message to another object. But why would we might want to do that?
SimpleDelegator from Ruby’s standard library in a previous post, and it feels like it’s well worth a dedicated post of its own. First, I will demonstrate how using delegate.rb can help us in designing strong-yet-flexible interfaces. Afterwards, looking at the source code itself, I will try to explain a bit of the magic that happens behind the scenes.
Suggest Me a Movie, Would You?
Let’s assume we are working on the backend part of a movie recommendation engine. In our simplified world, what we currently need to know about a
Movie is the (numeric) rating (score) it got from iMDb and Rotten Tomatoes. Assuming both scores are normalized on the same scale, we would also like to get a single simple number —
average_score — based on the average of the two external scores. It looks like this:
What we need next is a class that (perhaps after some regression modeling) holds an array of
RecommendedMovies, upon which we can perform relevant queries:
Ha, that’s pretty straightforward. We can pat ourselves on the shoulder for creating a dedicated object (i.e. not sticking to a primitive array) which has a clear and useful interface. Let’s get some data in and take it out for a test drive:
north_by_northwest = Movie.new('North by Northwest', 85, 100)
inception = Movie.new('Inception', 88, 86)
the_dark_knight = Movie.new('The Dark Knight', 90, 94)
recommended_movies = RecommendedMovies.new([north_by_northwest, inception, the_dark_knight])
We can query
recommended_movies in a simple manner:
=> #<Movie:0x007fbcf7048948 @name="North by Northwest", @imdb_score=85, @rotten_tomatoes_score=100>
RecommendedMovies seems to work as expected, but with one significant drawback: we initiated it with an array, but we lost all original
Array behavior: if we run
recommended_movies.count, we’ll get a
NoMethodError in return. This is somewhat limiting, as it’s very probable that we would benefit from using all those wonderful
Enumerable) methods upon
RecommendedMovies! We could implement
method_missing in our class, yet perhaps we could turn to a more elegant solution, found in Ruby’s standard library — delegate.rb.
This library offers two concrete solutions to our problem — both of them are implemented through inheritance. While
DelegateClass is worth a dive-in on it’s own, the simpler of the two is
SimpleDelegator and it suits the case in question very well. We could use it like this:
And… that’s pretty much it. Everything works the same as before, and now we have all the original array methods at hand. Basically, we applied the Decorator Pattern upon an array. Referring to the same data that we’ve prepared earlier, running
recommended_movies.count now simply returns
3. Notice that I’ve omitted the
initialize method since declaring and referring to the
movies instance variable is no longer needed! It’s as if
self in our
RecommendedMovies instances is the array with which we initiated the
Behind The Scenes
The source code for delegate.rb is available here; I will refer to it with line numbers, so you might want to keep it open on the next tab. A stroke on the
l key will facilitate jumping to a specific line. Furthermore, there some interesting commentary in that file that I recommend reading in case you fancy a deeper dive. I will analyze our usage of
SimpleDelegator from the bottom up.
As a result of inheriting from
SimpleDelegator, the ancestors chain for
RecommendedMovies looks like this:
=> [RecommendedMovies, SimpleDelegator, Delegator,
That’s quite a change from the initial version’s ancestors chain —
[RecommendedMovies, Object, Kernel, BasicObject]. The reason for the difference is that
SimpleDelegator inherits from another class —
Delegator (line 316) — which in turn inherits from
BasicObject (line 39). This is why
Kernel are out of the chain now. The unfamiliar
#<Module:0x007fed5005fc90> (which would probably appear slightly different on your machine, if you run this code) is an anonymous module, defined and included by the
Delegate class (line 53); it serves as a chopped-down version of the
Kernel is duplicated and placed in temporary variable (line 40); then, actions are performed on the variable’s class level (line 41) that un-define several methods from it (line 44, line 50). After these modifications, the updated
Kernel finally gets included in
Delegate. This explains the ancestors chain we’re seeing.
As noted earlier, I’ve omitted the
initialize method from my updated
RecommendedMovies class. Ruby will automatically call
initialize on a new object (i.e. after we called
new on a class), but since I did not implement an initialization method, it went up the ancestors chain to look it up, as expected.
SimpleDelegator doesn’t implement it as well, but
Delegator does (line 71). It is expecting a single argument,
obj, which is the argument with which the
RecommendedMovies instance was created — in our case, an
Movie objects — and that is the object we will delegate messages to.
Delegator#initialize simply calls the
__setobj__ method, passing the same
obj as an argument again. But
Delegator does not implement
__setobj__: if it was to receive such call, it would raise an error (line 176). This is because
Delegate serves as an abstract class. Its subclasses should implement
__setobj__ themselves, and indeed,
SimpleDelegator does that (line 340).
SimpleDelegator#__setobj__ simply stores
obj in an instance variable named
sd stands for SimpleDelegator). Remember, in our example,
self is still
As demonstrated earlier on, once our
recommended_movies object is born, we can use it as a decorated array. When we call the
best method upon it, Ruby locates it in the object’s class,
RecommendedMovies, and executes it for us. But when we call
count, Ruby won’t find it in our class. The interpreter then traverses through the ancestors chain looking up the method, but alas,
count is not defined in any of our class’ ancestors!
This is the point where
method_missing comes into play. If Ruby finishes the regular method lookup with no findings, it will not throw a
NoMethodError right away; instead, it will commence the lookup again, this time looking for
method_missing. If any of the classes in the ancestors chain define this method, it will be called. If not, we will receive that
NoMethodError after the search reaches its end at the top of the chain.
In our context, the
Delegator class defines
method_missing (line 78). First, it fetches the target object we will try to delegate to by calling
__getobj__ (line 80), which is implemented by
SimpleDelegator (line 318). Essentially, this method hands us back the target object stored in
@delegate_sd_obj. It will then try to call the method in question upon the the target object (line 83). If the target object doesn’t respond to the method,
Delegate#method_missing will check if
Kernel does and will call it accordingly (line 85). Otherwise, it will call
super (line 87), which, at this part of the narrative, will result in that
NoMethodError. Quite a journey!
There is some more code in
Delegate#method_missing, but this is the core of action. In his book Metaprogramming Ruby 2, Paolo Perrotta defines a Blank Slate as “a skinny class with a minimal number of methods” (p. 66). The
Delegate class uses this technique by inheriting from
BasicObject which eliminates unwanted surprises, but remember that at the same time, the intelligent implementation of
method_missing asks the target object whether it respond to a certain method, and that target object probably does inherit from
Object, like most Ruby objects. It is a complex interplay, but at the end of the day, the interface we end up with (i.e. the
RecommendedMovies class) is very simple and intuitive. Look out for those appropriate areas in your code where this pattern might come in handy— you probably have quite a few, and this refactor is usually very pleasing!
If you have any remarks or questions about this post, please use the comments! And if you found it interesting or useful, please support it by clicking the 👏 below.