Don’t you just hate it when someone reopens your perfect class? Well let’s do something about that.

So let’s say we have a class:

class Monkey
  def see
    'banana'
  end
end

And then we try to reopen it:

class Monkey
  def see
    'banana'
  end
end

class Monkey
  def see
    'dirty patch'
  end
end

And now our class behaves differently.

Monkey.new.see # => 'dirty_patch'

But what if we could do something like this:

class Monkey
  extend NotMonkeyPatchable
end

And when we look at the same scenario again:

class Monkey
  extend NotMonkeyPatchable

  def see
    'banana'
  end
end

class Monkey
  def see
    'dirty patches'
  end
end

Monkey.new.see # => 'banana'

Cool, our class resisted being reopened. But how? The module we extend our class with looks like this:

module NotMonkeyPatchable
  def method_added(name)
    @methods ||= {}
    if @methods[name]
      unpatch_method(name)
    else
      @methods[name] = instance_method(name)
    end
  end

  def unpatch_method(name)
    return if @unpatching
    @unpatching = true
    method = @methods[name] 
    define_method(name) { |*args, &block|
      method.bind(self).call(*args, &block)
    }
    @unpatching = false
  end
end

We utilize the method_added hook Ruby gives us. For each method you define on a class, it’s method_added method is called with the name of the method that was defined.

def method_added(name)
  @methods ||= {}
  if @methods[name]
    unpatch_method(name)
  else
    @methods[name] = instance_method(name)
  end
end

We store the first implementation for a name. If it already exists then it means someone is trying to redefine the method. And we don’t want that, so we unpatch it back to what it was.

def unpatch_method(name)
  return if @unpatching
  @unpatching = true
  method = @methods[name]
  define_method(name) { |*args, &block|
    method.bind(self).call(*args, &block)
  }
  @unpatching = false
end

The method we got through instance_method is unbound. In order to call it, it must first be bound to an object. So that’s what we do, we redefine the method again after it was patched, and inside we bind the original method object to self and call it. Since we are defining a method the method_added hook will be invoked again, and to avoid dropping into an infinite loop we guard ourselves with the @unpatching flag.

We probably need to add a mutex on this whole process of redefinition to be threadsafe, but I omitted it.

In fact, this even works as expected for subclasses.

class Gorilla < Monkey
  def see
    'jungle'
  end
end

Gorilla.new.see # => 'jungle'

class Gorilla
  def see
    'ketchup'
  end
end

Gorilla.new.see # => 'jungle'

And there you have it, no more monkey patches are going to mess up your precious code. :)