This is a terrible idea.

I was challenged to make nil stop being so whiny in Ruby, as if all . operators were &. operators.

I mean, that’s really quite easy:

class NilClass
  def method_missing(*args)
    self
  end
end

nil.something.another_thing
# => nil

Don’t do that.

But what if we did that?

Ok, let’s not do exactly that but what can we do that’s similar? Obviously we don’t want to actually change nil for everyone, but what if we could do it just on demand? That is, without breaking the rest of Ruby, what if we could declare a specific object as one for which we currently do not care about further nils?

nil is a singleton, so we actually can’t modify it unless doing so globally. But we can roll our own. Let’s start by establishing a pattern in a new object called “nothing”.

require 'singleton'

class NothingClass
  include Singleton

  def inspect
    "nothing"
  end

  def method_missing(*args)
    self
  end
end

# Give all objects the ability to know if they are the One True Nothing
module Kernel
  def nothing?
    self.eql?(NothingClass.instance)
  end
end

nothing = NothingClass.instance
# => nothing
nothing.nothing?
# => true
nothing.thing.another_thing
# => nothing
"hello".nothing?
# => false

We’ve defined a method nothing? on Kernel that operates similarly to nil? (also defined on Kernel) so we can check for nothingness. But how do we get there?

Remember, our goal is to mimic safe-navigation (&.) automatically. Our first attempt is a method that you can call when you’re in danger of being nil.

module Kernel
  def nothing?
    self.eql?(NothingClass.instance)
  end

  def nothingify
    nil? ? NothingClass.instance : self
  end
end

We’re going to use the hashie gem because it gives us a rich set of nested method calls that we might use to probe for data (similar to using ActiveRecord model associations and attributes)

require 'hashie'
data = Hashie::Mash.new({x: "hello", y: "world"})
data.x.nothingify.upcase.gsub(/[eE]/,'3')
# => "H3LLO"
data.a.nothingify.upcase.gsub(/[eE]/,'3')
# => nothing

Now if we have an object that might be nil, we can call nothingify on it to be safe from any immediate danger of NoMethodErrors on nil. But as little more than a glorified try, if our nil comes anywhere further down the chain, we’re still boned:

require 'hashie'
response = Hashie::Mash.new({data: {x: "hello", y: "world"}})
response.nothingify.data.x
# => "hello"
response.nothingify.oops.x
# NoMethodError: undefined method `x' for nil:NilClass
response.nothingify.oops.nothingify.x
# => nothing

The error occurs because response.nothingify.oops is still nil, since nothingify simply returned self. We can’t call x on nil.

What we really want is for a single call to nothingify to perpetually call nothingify on any downstream result.

Can we do that?

Deep Nothing

module DeepNothing
  CANT_TOUCH_THIS = [
    :nothingify,
    :deep_nothing,
    :nil?,
    :methods,
    :define_singleton_method,
    :convert_key,
  ]
  def deep_nothing
    methods.each do |method_name|
      next if CANT_TOUCH_THIS.include?(method_name)
      define_singleton_method(method_name) do |*args, &blk|
        super(*args, &blk).nothingify
      end
    end
    return self
  end
end

Object.include(DeepNothing)

module Kernel
  def nothing?
    self.eql?(NothingClass.instance)
  end

  def nothingify
    nil? ? NothingClass.instance : deep_nothing
  end
end

require 'hashie'
response = Hashie::Mash.new({data: {x: "hello", y: "world"}})
response.nothingify
# => #<Hashie::Mash data=#<Hashie::Mash x="hello" y="world">>
response.nothingify.data.x
# => "hello"
response.nothingify.oops.x
# => nothing

Success! (Still a terrible idea.)

Ok, a brief explanation. The method deep_nothing nothing-safes our object by redefining (almost) all of its methods with a wrapper that nothingifys the return value. So a nil return gets turned into nothing, and any other return value get re-nothingify‘d.

We only wrap almost all of the moethods because

  1. We can’t wrap nothingify itself (or any methods called therein, namely, nil? and deep_nothing) otherwise we’ll get in an infinite loop calling nothingify repeatedly
  2. We can’t redefine methods that we’re trying to use inside our method redefinition: methods and define_singleton_method.

Everything else gets wrapped. (Except for convert_key, which causes trouble putsing a Hashie::Mash for some reason that I cannot figure out).

Now we can nothingify an object once and thereafter be assured to not be bothered by that pesky, no good NoMethodError thing.

Back to the Singleton

One edge-case issue with this approach is singleton methods don’t work because they don’t have a super. Normally, super in this context refers to the original instance method defined by the class. For singleton methods, this doesn’t exist.

obj = Object.new
# define a stingleton method with no relation to the class
def obj.hello
  "world"
end

obj.hello
# => "world"
obj.nothingify.hello
# NoMethodError: super: no superclass method `hello' for #<Object:0x007fb472243280>

We can fix that by using method binding instead of super:

module DeepNothing
  CANT_TOUCH_THIS = [
    :deep_nothing,
    :nil?,
    :methods,
    :method,
    :nothingify,
    :define_singleton_method,
    :convert_key,
  ]
  def deep_nothing
    methods.each do |method_name|
      next if CANT_TOUCH_THIS.include?(method_name)
      saved_method = method(method_name).unbind
      define_singleton_method(method_name) do |*args, &blk|
        saved_method.bind(self).call(*args, &blk).nothingify
      end
    end
    return self
  end
end

Here, when we redefine a method, we first save a reference to the original method. We then call that original method inside our redefined method. No super, just the original result.

obj = Object.new
def obj.hello
  "world"
end

obj.hello
# => "world"
obj.nothingify.hello
# => "world"

And then there’s truthiness.

Nothing is true

One critical missing feature of our nothing object is that it evaluates to true in a boolean context.

print "Something" if nothing
# Something

Unfortunately there just isn’t a way around this (prove me wrong in the comments, I beg you!). Ruby, for all its flexibility, doesn’t let you do some things, and defining truthiness is one of them. Perhaps, in this day and age, we can take some comfort in that.