Key Takeaways
- Monkey patching, also known as gorilla or duck patching, is a process in dynamic languages like Ruby that allows code to be extended or modified at run-time without changing the original source. However, it’s not always the ideal solution due to issues such as sustainability.
- Hacking and monkey patching may be necessary due to bugs in a gem or in JRuby itself. Constraints in the working environment may also lead to the need for these measures, especially when it’s not possible to quickly download and use new releases of code.
- JRuby allows for monkey patching even in Java classes. However, it’s important to maintain consistency in projects when applying hacks and monkey patches. A good practice is to create a patches directory in the config/initializers directory of Rails applications and document the patches to avoid confusion in the future.
- Monkey patching in JRuby is done at runtime, which allows for more dynamic and adaptable code. However, it requires a high level of caution to avoid potential issues with code stability and maintainability. It’s recommended to use monkey patching sparingly and with a clear understanding of its potential impact on the code.
What is Monkey Patching
Monkey patching, gorilla patching or duck patching all refer to the same process of extending or modifying code at run-time without modifying the original source. Dynamic languages, such as Ruby, make this particularly easy to accomplish. However, it’s not all sunshine and rose petals in monkey patch land. In fact, in a 2008 blog post by Avdi Grimm titled, Monkeypatching is Destroying Ruby, he called out some of the issues with it, such as sustainability. Certainly a monkey patch can be useful or flat out necessary at times, but why? How do we get to a point where we have to rely on creating these wild patches in the first place?Why Hack and Monkey Patch
As you might expect, there are all sorts of reasons why you might find the need to hack and patch. I’ve personally run into the following categories of issues which have led me to apply various patches over the last year:- Bug in a gem
- Bug in JRuby
Bug in a Gem
Developers all know there’s going to be bugs, it’s inevitable. I must admit I’ve been quite impressed with the amount of open source software that works “out of the box”. But there are times when we run into bugs or other issues that we need to get around. Recently I ran into an issue with the mongo_session_store gem which provides Rails compatible session stores for MongoMapper and Mongoid. The specific problem I had was thatmongo_session_store
was trying to load MongoMapper which I hadn’t previously installed. This immediately stood out to me because I was using Mongoid, so I wasn’t expecting MongoMapper to be required.
I should rewind a little bit to explain how I narrowed the issue down to mongo_session_store. In the logs I just followed the stacktrace of an exception which led me here. At this point I knew where the problem came from, but I wasn’t sure why. I dug a little bit more to see how the code was setup and being required in mongo_session_store
. That’s when I discovered the mongosessionstore-rails3 module, which makes use of Ruby’s autoload feature. Here’s the code for most of the module:
[gist id=”1917678″]
It appears that the second if
block (which checks for the MongoMapperStore::Session
) causes the MongoMapperStore
class to be loaded which attempts to require 'mongo_mapper'
. I’m using this gem in a JRuby on Rails application which gets deployed as a Java WAR) file to Apache Tomcat. This particular bug only seems to occur when the application is deployed to Tomcat. If I run the application using WEBrick, I’ve had no issues.
At this point, I’m not exactly sure what’s causing the MongoMapperStore
to get loaded when running in Tomcat vs. WEBRick (even when running with JRuby), which is why I opened the issue on Github. Since this issue was actually stopping my application from working, I had to make a quick hack to get things moving along while I wait for the gem author to ( hopefully ) reproduce and fix the issue.
I looked through the history of the MongoSessionStore
module and saw the autoloading was added a few months ago. I essentially removed the autoloading and added back the standard require’s wrapped in begin-rescue blocks. Once I had made these changes, I modified the gemspec so I could rebuild the gem with a slightly different name (to make it a little more obvious we’ll need to get an updated version of the original gem in the future). After which, I built the gem and uploaded it to a private gem repository used at work, and things worked just fine.
Bug in JRuby
I’ve been using JRuby for the past year beginning with version 1.5.6 and now I’m up to 1.6.5. In fact some of my recent monkey patches have been applied to resolve issues which were fixed in JRuby 1.6.6 (although I don’t have it available at work just yet). One of the first things I do when I setup Tomcat to host my Rails applications is enable two-way SSL. You may be familiar with TLS/SSL (as used with HTTPS) in which a website presents a certificate to your browser. The browser checks to see if the certificate is valid (i.e. hasn’t expired, signed by a trusted CA and a few other things) and then finishes setting up the HTTPS connection (unless the certificate is invalid). However, two-way SSL (also known as mutual authentication) takes it a step further. With two-way SSL the server presents a certificate but it also requires the user to present a certificate as well. All of the users of the Rails applications I develop have their own personal certificate as well. In early 2011 I was all excited when I was ready to deploy my first Ruby on Rails application as a WAR to a Tomcat server (configured with two-way SSL). I fired up the server, tailed the logs, browsed to the site and BAM! [gist id=”1917754″] I hit up google and stumbled across JRUBY-5529 which was scheduled to be fixed in JRuby 1.6.6 (not available at the time). However, it wasn’t all bad news as someone (named Karl Baum) had posted a gist to monkey patch theBufferedIO
class:
[gist id=”969527″]
One of the great things about JRuby is that you can do just about anything you can do in Ruby including monkey patch Java classes.
Closing Notes
I’ve only shown a couple of examples, but I’ve run into several others (nokogiri,send_file
when using JRuby and more). The point is you will likely have to hack and monkey patch especially as you get into using more 3rd party gems or newer versions of JRuby.
One suggestion I’d like to close with is maintaining consistency in your projects. Whether working by yourself or with a team it’s a good idea to be consistent with where and how you apply hacks and monkey patches. With Rails applications, I like to create a patches
directory in the config/initializers
directory. Documenting the patches (or hacks) in a README, a wiki or some other site accessible to you, your team or other users will help avoid confusion and potential issues in the future.
Frequently Asked Questions (FAQs) about Hacks and Monkey Patching with JRuby
What is the main difference between monkey patching in JRuby and other languages like Python?
Monkey patching in JRuby is quite different from other languages like Python. In JRuby, monkey patching is done at runtime, which means you can modify or extend the behavior of an object or class while the program is running. This is different from Python where monkey patching is done at the source code level. This flexibility in JRuby allows for more dynamic and adaptable code, but it also requires a higher level of caution to avoid potential issues with code stability and maintainability.
Is monkey patching considered a good practice in JRuby?
The use of monkey patching in JRuby, like in any other language, is a matter of debate among developers. While it provides a powerful tool for modifying and extending existing code, it can also lead to unexpected behavior and hard-to-debug issues if not used carefully. Therefore, it’s generally recommended to use monkey patching sparingly and with a clear understanding of its potential impact on your code.
How can I avoid potential issues when using monkey patching in JRuby?
To avoid potential issues when using monkey patching in JRuby, it’s important to follow a few best practices. First, always make sure you understand the original behavior of the method you’re patching. Second, keep your patches as small and simple as possible to minimize the risk of introducing new bugs. Finally, always test your patches thoroughly to ensure they work as expected and don’t break anything else in your code.
Can I use monkey patching to add new methods to a class in JRuby?
Yes, you can use monkey patching in JRuby to add new methods to a class. This can be a powerful way to extend the functionality of existing classes without having to modify the original source code. However, it’s important to be careful when adding new methods to avoid potential conflicts with existing methods or future updates to the class.
What are some common use cases for monkey patching in JRuby?
Monkey patching in JRuby can be used in a variety of situations. Some common use cases include fixing bugs in third-party libraries, adding new functionality to existing classes, and modifying the behavior of methods for testing purposes. However, it’s important to remember that monkey patching should be used sparingly and with caution to avoid potential issues.
What is the impact of monkey patching on the performance of my JRuby application?
The impact of monkey patching on the performance of your JRuby application can vary depending on how it’s used. In general, monkey patching can potentially slow down your application if it’s used to modify frequently called methods, as the patched methods may take longer to execute. However, if used sparingly and wisely, the impact on performance can be minimal.
Can I undo a monkey patch in JRuby?
Undoing a monkey patch in JRuby can be tricky, as it requires restoring the original behavior of the patched method. This can be done by keeping a reference to the original method before applying the patch, and then using this reference to restore the method when the patch is no longer needed. However, this approach can be complex and error-prone, so it’s generally recommended to avoid the need to undo patches by using them sparingly and testing them thoroughly.
How does monkey patching in JRuby relate to the principle of open classes?
Monkey patching in JRuby is closely related to the principle of open classes, which is a feature of the Ruby language that allows classes to be reopened and modified at any time. This feature is what makes monkey patching possible in JRuby. However, while open classes provide a powerful tool for modifying and extending existing code, they also require a high level of caution to avoid potential issues with code stability and maintainability.
Can monkey patching in JRuby lead to conflicts with other patches or updates to the original code?
Yes, monkey patching in JRuby can potentially lead to conflicts with other patches or updates to the original code. This is because a monkey patch modifies the behavior of a method in a way that may not be compatible with other modifications or updates. To avoid such conflicts, it’s important to keep your patches as small and simple as possible, and to test them thoroughly to ensure they work as expected.
What are some alternatives to monkey patching in JRuby?
There are several alternatives to monkey patching in JRuby, depending on what you’re trying to achieve. For example, if you’re trying to add new functionality to a class, you could consider using inheritance or composition instead. If you’re trying to modify the behavior of a method for testing purposes, you could consider using stubs or mocks. These alternatives can often provide a safer and more maintainable way to achieve the same goals as monkey patching.
Craig Wickesser is a software engineer who likes to use Ruby, JRuby, Rails, JavaScript, Python, Java and MongoDB. He is also a moderator for RailsCasts, a former writer for InfoQ and a former editor for GroovyMag. You can check Craig's stuff out on his blog.