Delegation with BasicObject

Using BasicObject to build proxy objects

by Ray Zane

tl;dr Inheriting from BasicObject allows you to build proxy objects that delegate methods like #inspect, #to_s, and #is_a?.

If you're familiar with Ruby, you probably know that Object is the default root of all Ruby objects. So it's probably a top-level class, right? Nope. Object inherits from BasicObject.

Object.superclass #=> BasicObject

In this article, we'll explore how you can use BasicObject to implement proxy objects that delegate all of their methods to another class.

A Delegate Situation

Let's imagine you have a class called Dog:

class Dog
  attr_reader :name

  def initialize(name)
    @name = name

  def bark
    puts 'WOOF!'

Now, imagine that you want to call the #bark method whenever any method is called on an instance of Dog.

Surely, you could use inheritance to override every single method like so:

class DogSubclass < Dog
  def name

dog ='Fido')
#=> "Fido"

Great. That will solve our problem for now. But, what if Dog had hundreds of methods? Would you manually override every single method that Dog defines?

The Proxy Pattern

A more clever way to implement this feature would be to create a proxy.

What is a proxy pattern?

In short, a proxy forwards (or delegates) all method invocations to another object.

class AnnoyingDog
  def initialize(dog)
    @dog = dog


  def method_missing(meth, *args, &block)
    @dog.send(meth, *args, &block)

fido ='Fido')
#=> "Fido"

annoying_fido =
#=> "Fido"

When we call, Ruby knows that AnnoyingDog doesn't define a #name method. Therefore, #method_missing is dispatched. This is where we can make the dog #bark and delegate the #name method.

Let's try another example:

#=> "#<Dog:0x007fb3528d5a60>"

#=> "#<AnnoyingDog:0x007f998a04ee28>"

We've got two problems. First, AnnoyingDog#to_s returns a different result than Dog#to_s. Second, annoying_fido didn't bark.

As you can see, AnnoyingDog did not invoke #method_missing. This is because AnnoyingDog inherits from Object, which implements #to_s.

AnnoyingDog.instance_methods.include?(:to_s) #=> true

Back to Basics

As of Ruby 2.3, Object implements a total of 56 methods. To the contrary, BasicObject is extremely lightweight, containing just 8 critical methods.

#=> [:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__, :__binding__]

Now that we've identified the problem, let's try making AnnoyingDog inherit from BasicObject.

- class AnnoyingDog
+ class AnnoyingDog < BasicObject

And let's try our previous example again:

fido ='Fido')
#=> "#<Dog:0x007fb9841d9990>"

annoying_fido =
#=> "#<Dog:0x007fb06284ed28>"

If it barks like a dog, it must be a dog. The #to_s method is no longer defined for AnnoyingDog. Therefore, annoying_fido barks and behaves exactly like fido, but with slightly different behavior.

The Real World

If you're interested in seeing BasicObject in action, take a look at Baby Squeel.

We'd love to keep in touch!

Opt in for occasional updates from us. Privacy Policy