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.