Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recommended approach to monkey patching a class in ruby

I've noticed that there are two common ways to monkey patch a class in ruby:

Define the new members on the class like so:

class Array
   def new_method
     #do stuff
   end
end

And calling class_eval on the class object:

Array.class_eval do
   def new_method
      #do stuff
   end
end

I'm wondering if there is any difference between the two and whether there are advantages to using one approach over the other?

like image 689
stantona Avatar asked Apr 26 '12 16:04

stantona


People also ask

Is monkey patching a good idea?

Monkey patching is good for testing or mocking out behavior. They can be localized in factory/class decorators/metaclasses where they create a patched new class/object from another object to help with "cross-cutting concerns" in between ALL methods like logging/memoization/caching/database/persistance/unit conversion.

What is monkey patching in Ruby?

In Ruby, a Monkey Patch (MP) is referred to as a dynamic modification to a class and by a dynamic modification to a class means to add new or overwrite existing methods at runtime. This ability is provided by ruby to give more flexibility to the coders.

What is monkey patching give example?

Monkey patching can only be done in dynamic languages, of which python is a good example. Changing a method at runtime instead of updating the object definition is one example;similarly, adding attributes (whether methods or variables) at runtime is considered monkey patching.

Is monkey patching metaprogramming?

Monkey patching is the first step towards meta-programming - writing code which writes code.


2 Answers

Honestly, I used to use the 1st form (reopening the class), as it feels more natural, but your question forced me to do some research on the subject and here's the result.

The problem with reopening the class is that it'll silently define a new class if the original one, that you intended to reopen, for some reason wasn't defined at the moment. The result might be different:

  1. If you don't override any methods but only add the new ones and the original implementation is defined (e.g., file, where the class is originally defined is loaded) later everything will be ok.

  2. If you redefine some methods and the original is loaded later your methods will be overridden back with their original versions.

  3. The most interesting case is when you use standard autoloading or some fancy reloading mechanism (like the one used in Rails) to load/reload classes. Some of these solutions rely on const_missing that is called when you reference undefined constant. In that case autoloading mechanism tries to find undefined class' definition and load it. But if you're defining class on your own (while you intended to reopen already defined one) it won't be 'missing' any longer and the original might be never loaded at all as the autoloading mechanism won't be triggered.

On the other hand, if you use class_eval you'll be instantly notified if the class is not defined at the moment. In addition, as you're referencing the class when you call its class_eval method, any autoloading mechanism will have a chance to locate class' definition and load it.

Having that in mind class_eval seems to be a better approach. Though, I'd be happy to hear some other opinion.

like image 56
KL-7 Avatar answered Sep 21 '22 15:09

KL-7


Scope

One big difference that, I think, KL-7 did not point out is the scope in which your new code will be interpreted:

If you are (re)opening a class to manipulate it, the new code you add will be interpreted in the scope of the (original) class.
If you are using Module#class_eval to manipulate a class, the new code you add will be interpreted in the scope surrounding your call to #class_eval and will NOT be aware of the class-scope. If one does not know, this behavior might be counter-intuitive and lead to hard-to-debug errors.

CONSTANT    = 'surrounding scope'  # original class definition (uses class scope) class C   CONSTANT  = 'class scope'    def fun()  p CONSTANT  end end C.new.fun    # prints: "class scope"   # monkey-patching with #class_eval: uses surrounding scope! C.class_eval do   def fun()  p CONSTANT  end end C.new.fun    # prints: "surrounding scope"   # monkey-patching by re-opening the class: uses scope of class C class C   def fun()  p CONSTANT  end end C.new.fun    # prints: "class scope" 
like image 31
Andreas Rayo Kniep Avatar answered Sep 18 '22 15:09

Andreas Rayo Kniep