Be a smarter patch monkey

25 August 2008

git metaprogramming ruby

A project I'm working on requires some hard-core monkey-patching of Rails internals.

Monkey-patching is a dangerous occupation, and liable to cause new and intriguing bugs into previously-tested sane code.

I've been working on a smarter patch-monkey, known as Lemur.

The goal is to allow monkey-patched methods (currently only instance methods are supported) to be written in modules that are mixed in (as modules are) but allowing redefinition of methods in the patchee by the patcher module.

I may be ignorant of some Ruby to make it happen, but I've resorted to alias_method and remove_method, along with a handful of Ruby's reflection methods to swap methods in a reasonable, clean, and auditable fashion.

The specs demonstrate how it works. Assume a basic class:

  
class BasicClass
  def some_instance_method()
    # ...
  end
end

And a module to monkey-patch it

  
module PatchModule
  def some_instance_method()
    # ...
  end
end

Normally, Ruby will prefer a locally-defined method over a module mix-in, so you can't just include your patch module in, even using class_eval.

So, invite in the Lemur.

  
Lemur.patch_class(BasicClass, PatchModule)

And voila! Your class is monkey-patched by the nicely self-contained module, plus, it's tracked.

  
Lemur.patched_classes # [ BasicClass ]

And even more cool, you can get some patch-audit information for each patched class:

  
Lemur.patch_records( BasicClass ) # [ array of PatchRecords ]

Each PatchRecord keeps up with the patched class, the patched method name, the actual replaced Method object, along with the patch module and the patch method.

A total of 40min has been spent writing the code so far. The idea is to add better auditability, unpatching, and dealing with class methods, not just instance methods.

Now, when you encounter a weird bug, you can ask the Lemur where the oddness might've originated.

Want to pitch in and do some meta-programming to make future meta-programming less scary, fork my git repository and send me some pull requests.