Safer Monkey Patching

General Monkey Patching

In Java, classes are defined up front, compiled into bytecode, and remain unaltered.  Every instance created from a class adheres to that class.

Of course, Ruby is different.  In Ruby, you can define a class and later redefine it (and of course, you can modify individual instances of a class so that they differ from the class):

class car
   def drive
      "I'm driving!"
  end
end

# blah blah blah

# Reopen car and monkey patch it by adding a new method
class car
  def brake
     "hitting the brakes!"
  end
end

From the Java point of view, it looks odd to repeat the class keyword twice with the same class.  But in Ruby, the class keyword simply places you back into the scope of the class you are working with (e.g., car).   There is no significant distinction in using it a second time.

What becomes more of an issue is that any plug-in or any code in your Ruby space can monkey patch a top-level entity (e.g., String) and globally change or break behavior.  This could include over-writing an existing method.  Furthermore, it can be difficult to determine what entity made the change.

There are two approaches that were highlighted from Rake and ActiveRecord in Ruby Metaprogramming for safer monkey patching.

Monkey Patch with Module

I lived in Texas for a few years, and the people were really friendly.  In their honor, I could monkey patch String as follows:

class String
    def greeting
        "howdy ya'll!"
    end
end

"abc".greeting # => howdy ya'll!

Unfortunately, there is no easy way to tell what made this change.  This would make it more explicit:

module JohnRagan
    module String
        def greeting
            "howdy ya'll!"
        end
    end
end

class String
    include JohnRagan::String
end

puts "abc".howdy  # => howdy ya'll
puts String.ancestors # "JohnRagan::String" shows up as a module

Now, the modules involved in monkey patching show up via ancestors.

Preventing Monkey Patches

Common lore is that folks in New York City are not as friendly as in Texas.  Whether or not this is actually true, let’s have some fun with it.  Let’s say a NYC developer writes a plug-in your Rails app includes, which contains the following:

class String
    def greeting
        "go screw yourself!"
    end
end

"abc".greeting # => go screw yourself!

Now, your code is suddenly rude and devoid of its previous Southern charm as this monkey patch has overridden the previous one.

While there may not much be you can do to prevent monkey patching by others, you can check to see if a method already exists before you monkey patch it yourself.  Rake has a mechanism it uses to prevent unintended monkey patching overwrites on its part.  Rake itself monkey patches the class Module, adding a method called rake_extension(method), which takes the method name, and method to be extended as a code block, and prevents the monkey patching (with a warning) if the method already exists.  I would have used it as follows:

require 'rake'
class String
    rake_extension("greeting" ) do
        def greeting
            "howdy ya'll!"
        end
    end
end

If my developer friend in New York City had already added a greeting method, this logic would have ignored my monkey patch.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s