What if we want to modify how the sausage is made? Well, because Ruby classes are open, we can add/modify methods in existing pieces of code. We simply reopen the class and define (or redefine) the methods. This is often known as monkey patching. I'm not going to preach about how great or evil this "technique" is, but if you're interested, check out this article by Jeff Atwood.
Classes We Cannot Touch
Let's say I am using a class written by a friend called FriendClass. We'll assume it has two methods I'm allowed to use:- my_greeting()
- my_goodbye()
Here is the usage:
f = FriendClass.new f.my_greeting("Fred") # Hello, Fred. f.my_goodbye # Goodbye!
Reopen and Add a Method
Assume I don't have access to the implementation of the class. What if I want to add a new method to it? We just reopen the class:class FriendClass def my_insult puts "You suck." end end f = FriendClass.new f.my_insult # You suck.It might look like we've completely redefined the class, but Ruby knows that FriendClass has already been defined. It simply adds the new method to the existing definition. So, I can still use the methods originally defined by my friend as well:
f.my_insult # You suck. f.my_goodbye # Goodbye!
Reopen and Modify a Method
Now, I want to change the boring greeting in FriendClass. Just like adding new methods, we can completely change the behavior of existing methods using the same technique:f = FriendClass.new f.my_greeting("Fred") # Hello, Fred. class FriendClass def my_greeting(name) puts "Hey there, #{name}! How's it going?" end end f.my_greeting("Fred") # Hey there, Fred! How's it going?Voila! The class now has the behavior I want without changing the actual implementation of FriendClass.
Reopen and Add to Any Class
This type of modification can be done to any class in Ruby. This includes Gems and core Ruby classes. Let's say I wanted to be able to (naively) sum elements in my Arrays:[1,2,3,4].sum # undefined method `sum' for [1, 2, 3, 4]:Array (NoMethodError) class Array def sum sum = 0 self.each do |e| sum += e end sum end end [1,2,3,4].sum # 10
Monkey Patch a Ruby Class
If you want to modify the behavior of a method, but retain some of the existing functionality, there are several ways to do this. Each have pros and cons. The most popular way is to use alias:f = FriendClass.new f.my_greeting("Fred") # Hello, Fred. class FriendClass alias old_my_greeting my_greeting def my_greeting(name) puts "I used to say '#{old_my_greeting(name)}'. puts "Now I say, what's up, #{name}?" end end f.my_greeting("Fred") # I used to say 'Hello, Fred.'. # Now I say, what's up, Fred?By using alias, we can safely redefine my_greeting and still have access to the original version through old_my_greeting.
Alternative Solutions for Gems
Occasionally, you will be tempted to monkey patch a buggy Gem. Before doing so, it is important to consider alternative solutions. My favorite alternative to monkey patching is forking the Gem on Github, making a change, and adding the url to the Gemfile.Another solution is to directly modify the Gem code in your local environment. By using gemedit, you can quickly modify any Gem in your preferred editor. You can also use gem unpack to create a private copy of a Gem's contents in your current directory. Only do this if portability is not important for you project.
Please leave your corrections, suggestions, and opinions on monkey patching in the comments below!
Nice Post... Thanks!
ReplyDeleteVery helpful
ReplyDeleteSimple and superb. thanks
ReplyDelete