Ruby decorators, inheritance, mixins, Forwardable and SimpleDelegator

29 min. read

This is the story of an exploration of options I did recently. Explorations are fun and you learn a lot! I am writing this for myself, but maybe it is useful for you, imaginary reader. Who knows.

Some context, what am I trying to solve?

In a Sinatra app I am working on, I recently needed to extend functionality of a class that delegates a lot of methods to another class. In particular, a Person class that wraps another person object coming from a library, adding extra functionality like making urls for certain fields or translating an object returned by the library's person class into another object.

Basically, two things are happening here:

  • Delegating methods to the other class
  • Adding extra functionality

I tried to do the simplest thing that could possibly work, so I passed the library person as an argument to the constructor and manually delegated all methods, then added the new methods (I am using the symbol/hash syntax for the arguments of the constructor, as recommended by Sandi Metz in her book POODR):


module EP
  class Person
    def initialize(person:, # ...more arguments here)
      @person = person
      # ...initialize other arguments here
    end

    def id
      person.id
    end

    def name
      person.name
    end

    def image
      person.image
    end

    # ...etc
    # + extra methods that add new functionality
  end
end

This is already very ugly (the person class of the library I am using has a lot of methods), but what is more ugly is that there is a presenter/page class that uses this class to show information of the person and also adds more functionality (functionality whose place belongs in a page class but not necessarily in a person class). So I did the same:


# The person field of a person page responds to
# the same interface as an EP::Person
module Page
  class Person
    def initialize(person:, # ...more arguments here)
      @person = person
      # ...initialize other arguments here
    end

    def id
      person.id
    end

    def name
      person.name
    end

    # ...etc.

    # ...+ new methods
  end
end

The tests have the same problem, both look like:


  # ...

  it 'has a name' do
    person.name.must_equal('Jon Doe')
  end

  it 'has an image' do
    person.image.must_equal('http://example.com/jon-doe.jpg')
  end

  # ...

As you can see, there is a lot of repetition!

So, after following the red-green-refactor mantra, it's refactoring time again, just this time it's gonna be a very big refactoring that should be made or things will keep getting worse.

Also, it's a good practice to wrap libraries or library objects in your own classes to protect your application from them. These wrappers act as an interface living in the borders of your app with the exterior, like a translator or communicator. This way, any breaking changes of external things you have no control over can be handled in one place.

So I thought about all the possibilities I could use:

  • inheritance,
  • composition,
  • the Decorator Pattern,
  • the Forwardable module provided by the Ruby core,
  • during my research for this I also learnt about the existence of the SimpleDelegator class, which I had no idea of!

But just because you can use something doesn't mean you should, right?. What's the best for my specific use-case?

With this app I am starting to get into that point where you have enough code to see patterns and things that need to be refactored or die forever in the technical-debt ocean of doom. Aaaaaah! So I want to do things right, I want to find the right abstraction that will allow this code to be kept clean, changeable and easy to maintain in the future. Code that doesn't rot! In this post I am going to share what I found and what I decided to use at the end.

"The Next Developer" is the most important person in my life when thinking about this.

Exploring the possibilities

These are the things I explored:

The Decorator Pattern

The way to implement the Decorator Pattern in Ruby is very well explained in this Thoughtbot article about decorators, in particular, in the Plain Old Ruby Object decorator section.

But before I tried to give this a try, I wanted to check how my colleagues in another team were doing it. I hear them talking all the time about decorators they are using in their own code. Maybe they had already done this research and found a solution I could use!

However, when I looked at the code, it didn't look like they were using the Decorator Pattern, it looked more like a very intricate implementation of inheritance.

This is the code they are using, simplified:


class Decorator
  def initialize(thing_to_decorate:, # other arguments)
    @thing_to_decorate = thing_to_decorate
    # initialize other arguments
  end

  def decorated_thing
    ThingToDecorate.new(
      a_method: a_method,
      another_method: another_method,
      # etc.
    )
  end

  def a_method
    thing_to_decorate.a_method
  end

  def another_method
    thing_to_decorate.another_method
  end

  # ...etc., other methods here

  private
  attr_reader :thing_to_decorate, # other class fields
end

And then this is used like this:


class ThingDecorator < Decorator
  def a_method
    # do something with "thing_to_decorate.a_method"
  end

  # maybe add new specialization methods here
end

decorated_thing = ThingDecorator.new(thing_to_decorate).decorated_thing
# do something with the decorated thing

The reason why this is basically inheritance is because this is just selecting some methods in thing_to_decorate whose behaviour you want to change, creating a ThingToDecorate object which receives those modifications, and then returning that object, which is kind of an intricate way of implementing simple inheritance, since you would not need the decorated_thing at all. You could just create ThingToDecorate as a subclass of whatever is the class of thing_to_decorate, modify the methods you want to modify, and use instances of it directly. But maybe they needed this implementation for that particular codebase for some reason.

The only benefit I see of using this code is if you only wanted to expose some of the methods in thing_to_decorate, since inheritance exposes all the methods of the parent. But Ruby has much simpler ways of doing this as I will show below.

Inheritance can be understood as a kind of decoration indeed. But I discarded this approach because:

  • If I use this, I would still need to manually delegate all methods that I need from the parent.
  • The person class in the library I am using already has a defined signature for the constructor. With this approach, I would have to pass the person methods as arguments to the constructor (i.e. ThingToDecorate.new(id: id, name: name, #... etc.), where ThingToDecorate would be the person to decorate).

OK, so let's explore simple inheritance in my use-case:

Inheritance

Say the person class I am delegating methods to is called LibraryName. This would look something like:


module EP
  class Person < LibraryName::Person
    def initialize(
      # arguments needed by the parent class,
      # arguments needed by the subclass
    )
        super(
          # send arguments to the parent here
        )
        # initialize subclass arguments here
    end

    # ...plus new EP::Person methods
  end
end

The person class in the library is initialized with a document object that I don't have access to, all I can do is get an instance of a person. So this option was discarded.

This is the library constructor:


  def initialize(document, popolo = nil)
    @document = document
    @popolo = popolo
    # ...some metaprogramming here
  end

Things to consider:

  • I try to avoid inheritance in general, unless what I am writing is really a specialization and the parent class is as abstract as possible (i.e., we don't make instances of it), which the person class in the library isn't (Java makes this easy with explicit interface and abstract class types, but we are in rubyland here).
  • Even if I could have done this, the page class would have to inherit again from this person class to add its own functionality. Double inheritance can get double as bad!
  • Inheritance couples objects statically.
  • The subclass inherits ALL methods of the parent, and maybe you don't want to expose all of them.

OK, let's explore the next option.

Mixin modules

I first learnt about mixin modules in Rails and it blew my mind. However, depending on how you use them, they can either solve your problems or raise new ones and add complexity. They are another kind of inheritance, after all, just a bit more flexible, reusable, plug & play, etc.

The mixin module would be like:


module PersonInterface
  def id
    person.id
  end

  def name
    person.name
  end

  # ...etc.
end

And then we can include it in our person class:


module EP
  class Person
    def initialize(person:, # ...more arguments here)
      @person = person
      # ...initialize other arguments here
    end

    include PersonInterface
    # ...+ new EP::Person methods
  end
end

And in the presenter:


module Page
  class Person
    def initialize(person:, # ...more arguments here)
      @person = person
      # ...initialize other arguments here
    end

    include PersonInterface
    # ...+ new Page::Person methods
  end
end

This approach moves all the repeated delegation code to the mixin module. We can do the same thing for the tests:

The module tests:


module PersonInterfaceTest
  def test_has_a_name
    subject.name.must_equal('Jon Doe')
  end

  def test_has_an_image
    subject.image.must_equal('http://example.com/jon-doe.jpg')
  end
  # ...etc.
end

Our Person tests:


describe 'EP::Person' do
  let(:subject) { EP::Person.new(
    person: library_person_instance,
    # ...other arguments
  ) }
  include PersonInterfaceTest
  # ...tests for the new EP functionality
end

And for the presenter:


describe 'Page::Person' do
  let(:subject) { Page::Person.new(
    person: ep_person_instance,
    # ...other arguments
  ) }
  include PersonInterfaceTest
  # ...tests for the new Page functionality
end

Good things:

  • We can plug a module in other classes (like the page class); it is reusable.
  • We don't have to expose all the methods of the library's person class, just those we need.
  • It also cleans the tests because we can just inject the module where we want to test the interface and we avoid repeating code.

However, there are some ways this could go wrong:

  • For example, for the tests, you would have to make sure that the information returned by calling those methods on a person duck-type, whoever that is, is the same. This is not true for my application for reasons I have no space here to explain. So this option was discarded as well.
  • In the person page, we still need to manually delegate the new functionality in EP::Person.
  • Including modules suffers from the same problem that Rails: it adds "magic" to the combo. You see a method and you don't know where it came from.

OK, next:

Ruby's SimpleDelegator

This is a Ruby core class that helps you delegate methods to another object. You can still add extra args to the constructor as it uses inheritance.

The way you use it is, you subclass SimpleDelegator (which works more as an abstract class than the library's person class) and then you pass the "delegatable" object as an argument to super().

The tests would look like:


describe 'EP::Person' do
  let(:decorated_person) { EP::Person.new(
    person: library_person_instance,
    # other arguments
  )}

  it 'has a name' do
    decorated_person.name.must_equal('Jon Doe')
  end
end

And the class:


module EP
  class Person < SimpleDelegator
    def initialize(person:, # other arguments:)
      super(person)
      # initialize other arguments here
    end

    # ...+ extra EP::Person methods
  end
end

The SimpleDelegator class has some of the benefits of inheritance, plus others:

  • You don't have to manually delegate the methods of the parent class
  • You are coupled to a much simpler and stabler class, the SimpleDelegator
  • Revamps double inheritance, because the person page class would inherit again from SimpleDelegator, even if now we are passing an instance of an object that responds to the same interface as EP::Person
  • You have .__set_obj__() and .__get_obj__() methods available to manipulate the object you are delegating to, which give you more flexibility

The SimpleDelegator class has the same problems as inheritance, plus others:

  • You can not choose which methods to expose, it's all or nothing
  • I forsee that .__set_obj__() and .__get_obj__(), having so much power, could open a can of unstability worms if not used properly and may add complexity
  • binding.pry gets weird when you use it inside of a class that inherits from SimpleDelegator

A good article on the SimpleDelegator is Decorate your Ruby objects like a boss.

Let's investigate another option:

Forwardable

This is a module from the Ruby core that works pretty much like the SimpleDelegator class, but it allows you to choose which methods you want to expose from the decorated class by using the def_delegators method. It also allows you to rename methods in the parent using def_delegator. A nice article on Forwardable is Delegating all the things with Ruby Forwardable.

This is how you use it:


module EP
  class Person
    extend Forwardable
    def_delegators :@person, :id, :name, :image, # etc.
    def_delegator :@person, :area, :ep_area

    def initialize(person: person, # other arguments)
      @person = person
      # initialize other arguments
    end

    # ...+ extra EP::Person methods
  end
end

I like this approach, it is more expressive than the SimpleDelegator because you don't have magic methods appearing out of nowhere (the person class comes from a library, is not like we can go to our codebase and open a file where we can read which methods it defines).

Just for fun, let's also consider using method_missing (please don't kill me!).

method_missing

People don't like to use method_missing because it is a bit like adding or modifying methods in a Ruby core class on the fly, like when you monkey-patch the String or the Array class to do something fancy. You can read more about this particular way of meta-programming in Ruby Metaprogramming - Method Missing.

Again this is very similar to the two previous cases, we pass the object we want to decorate to the constructor, and then:


module EP
  class Person
    def initialize(person:, # other arguments)
      @person = person
      # initialize other arguments
    end

    def method_missing(method, *args, &block)
      person.send(method, *args, &block)
    end

    # ...+ extra EP::Person methods
  end
end

You get all the methods of the parent with this approach.

I don't think I would use it for my own classes, but I can imagine myself using it to objectify a hash, so that I can call the keys of that hash as if they where methods. For example, to objectify a JSON response.

Could be handy to have a class out of a hash like this, using .to_s (or .to_sym, depending on the case):


class HashAsAnObject
  def initialize(hash =  {})
    @hash = hash
  end

  def method_missing(method, *args, &block)
    hash.fetch(method.to_s, nil)
  end

  private

  attr_reader :hash
end

How can this go wrong:

  • It is a Kernel method, so you are basically meta-programming. With great power comes great responsability
  • Can be hard to debug, if there is no method in the decorated object (or in the case of HashAsAnObject, if there is no such key in the hash). Since the method call is being called on the decorator, but the decorated object is the one raising the error. It may be painful to track down where the problem resides.
  • This gives you only one level objectification (I'm making up words here, don't worry). If some key points to a subhash, the method associated to that key will return a hash.
  • If you add this method, you have to remember to also define respond_to_missing, as detailed in this Thoughtbot blog post.

How you can solve some of the previous points:

  • The nice thing about doing this to a hash as opposed to a class that you create, is that you can use the approach that Avdi Grimm uses in his book Confident Ruby, where he passes a block to the .fetch() method, to give him more flexibility, for better error handling, etc. Like this:

    
    # Using a customized fetch:
    def method_missing(method, *args, &block)
      hash.fetch(method.to_s) do
        raise Error, "#{hash.class} does not respond to #{method}"
      end
    end
    

    Hopefully this helps with debugging!

  • We could play some inception game here, and when a key returns a subhash, convert those subkeys to methods as well. As pointed out in this StackOverflow answer:
    
    module H
      def method_missing sym,*
        r = fetch(sym){fetch(sym.to_s){super}}
        Hash === r ? r.extend(H) : r
      end
    end
    
    the = { answer: { is: 42 } }
    the.extend(H)
    the.answer.is # => 42
    

With inheritance, SimpleDelegator and missing_method, if a method is defined here with the same name as another method in the parent or decorated class, it will overwrite the method. So that is one thing to keep in mind, because these are the three options that hide the parent or decorated object methods from you, so if you add new methods to the decorators, beware of the naming.

What would I use?

I feel really tempted to use method_mising for the JSON objects that I have in this particular app, whose hashes don't have more than two levels. And I think Forwardable is the best approach for my EP::Person and Page::Person classes. But I will give it a second thought.

Comments