SoFunction
Updated on 2025-03-04

Monkey Patch Monkey Patch Programming Method and Its Application in Ruby

What is Monkey Patch? In dynamic languages, the function is added and changed without modifying the source code.

Purpose of using monkey patches:
1. Additional functions
2. Functional changes
3. Correct program errors
4. Add hooks to execute some other processing while executing a certain method, such as printing logs, implementing AOP, etc.
5. Cache. When the calculation is large and the results after settlement can be used repeatedly, replacing the method after one calculation can improve the processing speed.

Ruby's classes are open classes, that is, you can add content at will after the class definition, which makes it particularly easy to use monkey patches in Ruby. In addition, Ruby also provides the function of operating methods, classes and modules, making us more handy with monkey patches. The basic functions provided by Ruby are as follows:
alias: Give the method another alias
include: Methods to introduce other modules
remove_method: Cancel the method in this class
undef:Cancel method
     
Using Monkey Patch in Ruby
The scene I encountered at that time was like this:

Our company uses the third-party library fog to perform EC2 operations. Many commands such as creating an instance need to set the instance type parameter. In fog, all types of EC2 are defined in the FLAVORS array of fog/aws/models/compute/. If the set type is not in the FLAVORS array, fog will be regarded as an invalid parameter and an error will be reported.

Later, Amazon released a new instance type D2. Although Ruby's third-party community is very active, the development community of fog still did not add D2 to it in time; and our company's work urgently needs to use D2 type instances.

The background is explained, let’s see what solutions are available.

Method 1: We can submit a Pull Request to fog to add a new type.

But this method doesn't work. The version dependency of knife-ec2 on fog we use must be 1.25.*, but fog has been updated to 1.31.0, and fog has changed greatly from 1.27.0. Obviously, it is impossible for us to wait for the knife-ec2 upgrade to support the new version of fog, so we submitted a Pull Request update to fog that would not solve the problem.

Method 2: Manually update the old version of fog Since the latest version of fog cannot be used, we can manually edit the fog version 1.25 and then package it into a Gem for use. This method is easier to operate than the previous method, but it is not easy to maintain when the problems arise. For a very small change, adding your own code to a third-party library always makes people feel that it is not "clean" enough.

Finally, with the guidance of my colleagues, I adopted the third method, namely Monkey Patch. I added a file lib/PROJECT_NAME/monkey_patches/ to my company's Ruby project, and then added the following code to modify fog/aws/models/compute/flavors:

require 'fog/aws/models/compute/flavors'

class Object

 def redef_without_warning(const, value)
  mod = self.is_a?(Module) ? self : 
  (:remove_const, const) if mod.const_defined?(const)
  mod.const_set(const, value)
 end
end

module Fog
 module Compute
  class AWS
   NEW_FLAVORS = FLAVORS + [
    {
     :id => "",
     ...
    },
    {
     :id => "d2.2xlarge",
     ...
    },
    {
     :id => "d2.4xlarge",
     ...
    },
    {
     :id => "d2.8xlarge",
     ...
    }
   ]

   redef_without_warning :FLAVORS, NEW_FLAVORS

  end
 end
end

Summarize
By adding a Monkey patch to our own code, we successfully implemented the dynamic addition of new instance types to the fog. Our company can finally use fog to create a D2 type machine; and the code changes to this method are the smallest and easier to maintain.

Monkey Patch is not the perfect solution, it introduces some pitfalls. So this technique is still controversial in the field of software engineering. However, I still think Monkey Patch is a good zero-time solution.