How to build a Null Object library like Mimic

If you're not familiar with the Null Object pattern, this will be an eye opener. Before I jump into Mimic, let's just get on the same page...

I've written about this idea in: Avoiding errors when forwarding to missing objects

BUT you don't need to jump off to read that. Here's a super quick explanation of a problem that could be solved with a Null Object.

You've used a method that returns an array of objects. Let's call ours gimme.

You run gimme and it shoots off to the internet or your database or somewhere and returns a collection of 3 people. Or so you think.

You want to iterate over those people and print out their names so you write some code to do it:

gimme.each { |person|
  print person.name
}

Unfortunately, when you run your code it blows up. One of those person objects doesn't respond to name and you get an error:

NoMethodError (undefined method `name' for nil:NilClass)

Ugh. One of those objects was nil and doesn't have a name.

Who knows where the problem is (it is made up after all) but something like a Null Object might make things work better for you.

A Null Object could stand in place of that nil object in your collection. Your solution would be that the Null Object instance would respond to name and return something useful. Perhaps it says "oopsie! this one is nil" or maybe it returns an empty string, or whatever makes sense for your application.

The solution could come in different ways but one might be to change the code to do print PossiblyAPerson(person).name ... or something like that. Wrap the object to make sure that print won't blow up when it tries to output person.name.

Enter Mimic

Here's the description from the source code:

Copy a class's instance interface to an anonymous, new object that acts as a substitutable mimic for the class

This is telling us that Mimic will create a new object that acts just like another one; exactly what we want from a Null Object.

I wanted to see how it works. So I dived in to the source.

I started reading Mimic source code as of commit f0ef642d351064f047fd07f1ef97dbb94bff15e6.

I first looked at mimic.rb, which was kind of boring.

require 'securerandom'

require 'mimic/preserved_methods'
require 'mimic/subject_methods'
require 'mimic/class'
require 'mimic/build'
require 'mimic/void'
require 'mimic/remove_methods'
require 'mimic/void_methods'
require 'mimic/mimic'

It merely requires other files. This means I'll need to go hunting around to find the interesting stuff.

What I did find interesting about this file is that the project's namesake is required last: 'mimic/mimic'. This means that it's likely that the Mimic module will be able to safely depend on things required above it.

Regardless, I started reading mimic/preserved_methods.rb and mimic/subject_methods.rb first.

They were not exciting. For example, I don't yet know why this code is relevant or how it's used:

module Mimic
  def self.preserved_methods
    @preserved ||= (Object.instance_methods << :method_missing).sort
  end
end

Then I saw this in mimic/subject_methods.rb and at least something looked familiar from the first file I read:

module Mimic
  def self.subject_methods(cls)
    instance_methods = cls.instance_methods.sort
    instance_methods - Mimic.preserved_methods
  end
end

This one uses Mimic.preserved_methods which was the first one I read. So at least we're beginning to make a connection between the files.

Then I hit some interesting parts in mimic/class.rb.

Feel free to tag along and look at the source yourself but I'm going to break it into parts to figure out what's interesting and what we can learn.

First things first, the top of the file:

module Mimic
  module Class
    def self.build(subject_class, &blk)
      define_class(subject_class, &blk)
    end

This is a module definition. So it could mean we could include or extend using Mimic::Class elsewhere in this source code or perhaps the project that pulls this library in might do that.

But the first method we see uses def self.build which means we'll really only be using it with Mimic::Class.build

It appears to be just a convenient name, however. The build method takes a class and a block and just passes it on to another method.

If you're familiar with the Forwardable library from Ruby's standard library (which I wrote about here in Ruby Forwardable deep dive) you'd know that you could also write this same method like this:

require 'forwardable'
module Mimic
  module Class
    extend SingleForwardable
    single_delegate [:build] => self

The array of forwarded methods could grow easily without needing to write each method out yourself. That's weird code, though. Forwarding a message to yourself might make you wonder why all that code is there in the first place, and it would just complicate this code.

Conveniently located below build is define_class:

def self.define_class(subject_class, &blk)
  mimic_class = ::Class.new(subject_class, &blk)
  set_constant(mimic_class, subject_class)
  mimic_class
end

And here's where we get into "metaprogramming"...

That Class.new means we're dealing with metaprogramming. It creates an anonymous class, or in other words a class constant with no name. "Metaprogramming" typically means that the program changes itself when it runs. Sometimes people shorten that and say "code that writes code."

There's not a whole lot to read in this define_class method beyond creating that anonymous class. But it does point us to a set_constant method, so let's look at that and see what we learn.

def self.set_constant(mimic_class, subject_class)
  class_id = mimic_class.object_id
  class_name = class_name(subject_class, class_id)

  unless self.const_defined?(class_name, false)
    self.const_set(class_name, mimic_class)
  end

  class_name
end

The interesting things that stand out to me are a method called class_name and then the code checks if a constant is already defined. If it isn't defined the code will set one.

But because there are several references to the value for class_name, we should probably take a look at that:

def self.class_name(cls, class_id)
      if cls.name.nil?
        return "C#{class_id}"
      else
        return "#{cls.name.gsub('::', '_')}_#{class_id}"
      end
    end
  end
end

This method checks if the received cls has a name that is nil, and returns a value or if it is not nil, substitutes some parts of the string and adds the provided class_id.

When I read code, I often mentally pronounce it. So cls is a bit of a stumbling block for me. How do I pronounce that? That short name could also lack clarity if this were a longer method. If I were to write something similar to this I would likely use klass. We can't use class because that's a keyword in Ruby that would cause the interpreter to expect that it was defining a class. But we're just referencing a class that was passed into this method, so we've got to pick something that's clear and doesn't conflict with a keyword. And at least it doesn't say clazz. Blech.

So why is it checking if a class name is nil? When would that ever happen?

Anonymous classes have no name. Try running this code:

Class.new.name

The result of that will be nil.

If we jump back up through the methods we'll see that set_constant receives subject_class from where it is called in define_class. Mimic::Class doesn't have control over what objects will be sent to the build or define_class methods so it needs to protect itself against working with nil like the name of an anonymous class.

So the class_name method will return the combination of "C" and the provided class_id argument with something like "C1234".

If the provided cls object does have a name, this method will return the name where :: is replaced with _ and the class_id appended to the end; from My::Awesome::Stuff to My_Awesome_Stuff_1234.

Let's jump back to set_constant again.

def self.set_constant(mimic_class, subject_class)
  class_id = mimic_class.object_id
  class_name = class_name(subject_class, class_id)

  unless self.const_defined?(class_name, false)
    self.const_set(class_name, mimic_class)
  end

  class_name
end

Everything in Ruby has an object_id. It's a useful way to get a unique identifier for something.

The class passed in as the mimic_class argument is the anonymous class from ::Class.new in define_class.

So the class_id will always refer to a new object. There's no worry that we'll generate a class_name that conflicts with an already existing one because each new object will have it's own object_id.

Next, that const_defined? check looks only in the current namespace. By default const_defined? will look for constants with the given name in the current namespace or any ancestor namespace. When you pass a second argument of false to const_defined? it will not search any ancestor namespaces.

Then self.const_set will set the constant for that anonymous mimic_class to the name from class_name in the current Mimic::Class namespace. This means that a generated name like "C1234" will live as Mimic::Class::C1234.

There's one last interesting thing to note about this set_constant method. It returns a string.

If you compare set_constant to const_set, the first returns a string but the latter returns the constant that was set. This could create a point of confusion. I have an early expectation that both of these methods will set constants but what they return may be critical to how I use them in my code. Without knowing the rest of the Mimic code well, it's difficult to say if this is a problem. But it is something to consider when building your own tools like this.

Now that we know how set_constant and class_name work, let's go back again, finally, to define_class.

def self.define_class(subject_class, &blk)
  mimic_class = ::Class.new(subject_class, &blk)
  set_constant(mimic_class, subject_class)
  mimic_class
end

We've seen that the mimic_class is created with ::Class.new but that :: is important to note.

Without that :: at the beginning of Class, Ruby would look for the first constant it finds named Class and use that. Unfortunately that would mean Mimic::Class because it would look in the current scope first. And in this case Mimic::Class is a module and not a class so not only would it be the wrong Class constant, but it also wouldn't respond to new.

One way to get around needing that would be to name this current module something else. Perhaps Mimic::ClassBuilder could work since that's what's happening here. Or maybe Mimic::Classes since this is the namespace used for the generated classes. Or maybe one :: is just fine to get around figuring out a different name.

These decisions matter and weighing the pros and cons of each decision is an important part of building a useful library that communicates intent and purpose well.

The last bit to note here is that the arguments to Class.new provide a parent class and a place to define methods for the new class.

If Mimic could know the name of the class it needed to build AND the methods that needed to be defined in that block, the code could look like this:

def self.define_class(subject_class, &blk)
  class TheClassName < subject_class
    def some_method_definition_from_the_block
      # ¯\_(ツ)_/¯
    end
  end
end

Since Mimic has no way of knowing how you'll use it and what constant names it needs to create... metaprogramming to the rescue! Using Class.new with a parent class and a block of code is a fantastic way to build up the code that you need to exist when your program runs.

This is one of the things that makes Ruby so flexible and fun.

But there is a danger. The code we've seen in Mimic does a great job of making sure that constants are created in a way that is easy for you to debug later. Each generated class is given a name that makes sense and can be found in a particular namespace. It's important to think through the impact of the metaprogramming techniques that you use to create a program and what you leave behind to debug.

There's more to Mimic but we've gotten though some important aspects of building a Null Object library.

The only part left, after creating the class that you need, is to define what methods should do when called on a Null Object.

Should they return nil? Should they return an empty string? Should they return a placeholder value or a warning?

When we discussed this problem above we saw PossiblyAPerson(person).name which we could make return "oopsie! this one is nil".

Here's a NotAPerson class and PossiblyAPerson method that can support this:

class NotAPerson
  def method_missing(*args)
    return "oopsie! this one is nil"
  end
end
def PossiblyAPerson(object)
  if object.is_a?(Person)
    object
  else
    NotAPerson.new 
  end
end

The problem with that is the NotAPerson object will respond to any method with "oopsie! this one is nil". Even the methods that Person does not implement will have that response.

When we use a Null Object to stand in for another object, we want it to behave as closely as possible to the real thing without breaking our code. But this code has a method interface that allows literally any method to be called on it. And that could be confusing if we need to take a look at it later and debug some problem.

Let's make our own library like Mimic and call it Imitate. We want it to create Null Object classes that will act like the class we give to it.

module Imitate
  def self.new(parent_class, &block)
    klass = Class.new(parent_class, &block)
    parent_class.instance_methods(false).each do |name|
      klass.define_method name do
        "oopsie! this one is nil"
      end
    end
    klass
  end
end

This code creates an anonymous class and defines every method from the provided parent_class to return the string we want. And here's how we'd use it:

NotAPerson = Imitate.new(Person)

def PossiblyAPerson(object)
  if object.is_a?(Person)
    object
  else
    NotAPerson.new
  end
end

Now we'll have objects that act like a person, but provide our warning message for every method.

Mimic does a lot more than Imitate. And now that we've walked through the structure and simple implementation we can start learning more about how Mimic works.

Look back and compare what Imitate doesn't do that Mimic does. Then dive in deeper to Mimic and learn more.

Understanding metaprogramming and both how and when to use it is critical to being able to build useful tools and solve difficult problems in Ruby. I often encorage everyone to build their own tools or know how to contribute to the ones they already use. That's why I'm building the Ruby Metaprogramming MasterClass to help developers develop knowledge and experience.