<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cameron Dutro</title>
    <description>The latest articles on DEV Community by Cameron Dutro (@camertron).</description>
    <link>https://dev.to/camertron</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F683215%2F12e63f3e-460f-4e28-8b5a-d97ea3db992c.jpeg</url>
      <title>DEV Community: Cameron Dutro</title>
      <link>https://dev.to/camertron</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/camertron"/>
    <language>en</language>
    <item>
      <title>Responsible Monkeypatching in Ruby</title>
      <dc:creator>Cameron Dutro</dc:creator>
      <pubDate>Tue, 31 Aug 2021 12:14:04 +0000</pubDate>
      <link>https://dev.to/appsignal/responsible-monkeypatching-in-ruby-902</link>
      <guid>https://dev.to/appsignal/responsible-monkeypatching-in-ruby-902</guid>
      <description>&lt;p&gt;When I first started writing Ruby code professionally back in 2011, one of the things that impressed me the most about the language was its flexibility. It felt as though with Ruby, everything was possible. Compared to the rigidity of languages like C# and Java, Ruby programs almost seemed like they were &lt;em&gt;alive&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Consider how many incredible things you can do in a Ruby program. You can define and delete methods at will. You can call methods that don't exist. You can conjure entire nameless classes out of thin air. It's absolutely wild.&lt;/p&gt;

&lt;p&gt;But that's not where the story ends. While you can apply these techniques inside your own code, Ruby also lets you apply them to anything loaded into the virtual machine. In other words, you can mess with other people's code as easily as you can your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Monkeypatches?
&lt;/h2&gt;

&lt;p&gt;Enter the &lt;em&gt;monkeypatch&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In short, monkeypatches "monkey with" existing code. The existing code is often code you don't have direct access to, like code from a gem or from the Ruby standard library. Patches are usually designed to alter the original code's behavior to fix a bug, improve performance, etc.&lt;/p&gt;

&lt;p&gt;The most unsophisticated monkeypatches reopen ruby classes and modify behavior by adding or overriding methods.&lt;/p&gt;

&lt;p&gt;This reopening idea is core to Ruby's object model. Whereas in Java, classes can only be defined once, Ruby classes (and modules for that matter) can be defined multiple times. When we define a class a second, third, fourth time, etc, we say that we're &lt;em&gt;reopening&lt;/em&gt; it. Any new methods we define are added to the existing class definition and can be called on instances of that class.&lt;/p&gt;

&lt;p&gt;This short example illustrates the class reopening concept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sounds&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;honk&lt;/span&gt;
    &lt;span class="s2"&gt;"Honk!"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sounds&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;squeak&lt;/span&gt;
    &lt;span class="s2"&gt;"Squeak!"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;sounds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;sounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;honk&lt;/span&gt;    &lt;span class="c1"&gt;# =&amp;gt; "Honk!"&lt;/span&gt;
&lt;span class="n"&gt;sounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;squeak&lt;/span&gt;  &lt;span class="c1"&gt;# =&amp;gt; "Squeak!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that both the &lt;code&gt;#honk&lt;/code&gt; and &lt;code&gt;#squeak&lt;/code&gt; methods are available on the &lt;code&gt;Sounds&lt;/code&gt; class through the magic of reopening.&lt;/p&gt;

&lt;p&gt;Essentially, monkeypatching is the act of reopening classes in 3rd-party code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is Monkeypatching Dangerous?
&lt;/h2&gt;

&lt;p&gt;If the previous sentence scared you, that's probably a good thing. Monkeypatching, especially when done carelessly, can cause real chaos.&lt;/p&gt;

&lt;p&gt;Consider for a moment what would happen if we were to redefine &lt;code&gt;Array#&amp;lt;&amp;lt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# do nothing 😈&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these four lines of code, every single array instance in the entire program is now broken.&lt;/p&gt;

&lt;p&gt;What's more, the original implementation of &lt;code&gt;#&amp;lt;&amp;lt;&lt;/code&gt; is gone. Aside from restarting the Ruby process, there's no way to get it back.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Monkeypatching Goes Horribly Wrong
&lt;/h2&gt;

&lt;p&gt;Back in 2011, I worked for a prominent social networking company. At the time, the codebase was a massive Rails monolith running on Ruby 1.8.7. Several hundred engineers contributed to the codebase on a daily basis, and the pace of development was very fast.&lt;/p&gt;

&lt;p&gt;At one point, my team decided to monkeypatch &lt;code&gt;String#%&lt;/code&gt; to make writing plurals easier for internationalization purposes. Here's an example of what our patch could do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;replacements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;horse_count: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;horses: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;one: &lt;/span&gt;&lt;span class="s2"&gt;"is 1 horse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;other: &lt;/span&gt;&lt;span class="s2"&gt;"are %{horse_count} horses"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# "there are 3 horses in the barn"&lt;/span&gt;
&lt;span class="s2"&gt;"there %{horse_count:horses} in the barn"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;replacements&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We wrote up the patch and eventually got it deployed into production... only to find that it didn't work. Our users were seeing strings with literal &lt;code&gt;%{...}&lt;/code&gt; characters instead of nicely pluralized text. It didn't make sense. The patch had worked perfectly well in the development environment on my laptop. Why wasn't it working in production?&lt;/p&gt;

&lt;p&gt;Initially, we thought we'd found a bug in Ruby itself, only later, to find that a production Rails console produced a different result than a Rails console in development. Since both consoles ran on the same Ruby version, we could rule out a bug in the Ruby standard library. Something else was going on.&lt;/p&gt;

&lt;p&gt;After several days of head-scratching, a co-worker was able to track down a Rails initializer that added &lt;em&gt;another&lt;/em&gt; implementation of &lt;code&gt;String#%&lt;/code&gt; that none of us had seen before. To further complicate things, this earlier implementation also contained a bug, so the results we saw in the production console differed from Ruby's official documentation.&lt;/p&gt;

&lt;p&gt;That's not the end of the story though. In tracking down the earlier monkeypatch, we also found no less than three others, &lt;em&gt;all patching the same method.&lt;/em&gt; We looked at each other in horror. How did this ever work??&lt;/p&gt;

&lt;p&gt;We eventually chalked the inconsistent behavior up to Rails' eager loading. In development, Rails lazy loads Ruby files, i.e., only loads them when they are &lt;code&gt;require&lt;/code&gt;d. In production, however, Rails loads all of the app's Ruby files at initialization. This can throw a big monkey wrench into monkeypatching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consequences of Reopening a Class
&lt;/h2&gt;

&lt;p&gt;In this case, each of the monkeypatches reopened the &lt;code&gt;String&lt;/code&gt; class and effectively replaced the existing version of the &lt;code&gt;#%&lt;/code&gt; method with another one. There are several major pitfalls to this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The last patch applied "wins", meaning, behavior is dependent on load order&lt;/li&gt;
&lt;li&gt;There's no way to access the original implementation&lt;/li&gt;
&lt;li&gt;Patches leave almost no audit trail, which makes them very difficult to find later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not surprisingly, perhaps, we ran into all of these.&lt;/p&gt;

&lt;p&gt;At first, we didn't even know there were other monkeypatches at play. Because of the bug in the winning method, it appeared the original implementation was broken. When we discovered the other competing patches, it was impossible to tell which won without adding copious &lt;code&gt;puts&lt;/code&gt; statements.&lt;/p&gt;

&lt;p&gt;Finally, even when we did discover which method won in development, a different one would win in production. It was also programmatically difficult to tell which patch had been applied last since Ruby 1.8 didn't have the wonderful &lt;code&gt;Method#source_location&lt;/code&gt; method we now have.&lt;/p&gt;

&lt;p&gt;I spent at least a week trying to figure out what was going on, time I essentially wasted chasing an entirely avoidable problem.&lt;/p&gt;

&lt;p&gt;Eventually, we decided to introduce the &lt;code&gt;LocalizedString&lt;/code&gt; wrapper class with an accompanying &lt;code&gt;#%&lt;/code&gt; method. Our &lt;code&gt;String&lt;/code&gt; monkeypatch then simply became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;localize&lt;/span&gt;
    &lt;span class="no"&gt;LocalizedString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When Monkeypatching Fails
&lt;/h2&gt;

&lt;p&gt;In my experience, monkeypatches often fail for one of two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The patch itself is broken.&lt;/strong&gt; In the codebase I mentioned above, not only were there several competing implementations of the same method, but the method that "won" didn't work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assumptions are invalid.&lt;/strong&gt; The host code has been updated and the patch no longer applies as written.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at the second bullet point in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Even the Best-Laid Plans...
&lt;/h2&gt;

&lt;p&gt;Monkeypatching often fails for the same reason you reached for it in the first place — because you don't have access to the original code. For precisely that reason, the original code can change out from under you.&lt;/p&gt;

&lt;p&gt;Consider this example in a gem that your app depends on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sale&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;discount_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tax_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="vi"&gt;@discount_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discount_pct&lt;/span&gt;
    &lt;span class="vi"&gt;@tax_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tax_rate&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total&lt;/span&gt;
    &lt;span class="n"&gt;discounted_amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sales_tax&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discounted_amount&lt;/span&gt;
    &lt;span class="vi"&gt;@amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="vi"&gt;@discount_pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sales_tax&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@tax_rate&lt;/span&gt;
      &lt;span class="n"&gt;discounted_amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="vi"&gt;@tax_rate&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, that's not right. Sales tax should be applied to the full amount, not the discounted amount. You submit a pull request to the project. While you're waiting for the maintainer to merge your PR, you add this monkeypatch to your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sale&lt;/span&gt;
  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sales_tax&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@tax_rate&lt;/span&gt;
      &lt;span class="vi"&gt;@amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="vi"&gt;@tax_rate&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works perfectly. You check it in and forget about it.&lt;/p&gt;

&lt;p&gt;Everything is fine for a long time. Then one day the finance team sends you an email asking why the company hasn't been collecting sales tax for a month.&lt;/p&gt;

&lt;p&gt;Confused, you start digging into the issue and eventually notice one of your co-workers recently updated the gem that contains the &lt;code&gt;Sale&lt;/code&gt; class. Here's the updated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sale&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;discount_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_tax_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="vi"&gt;@discount_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discount_pct&lt;/span&gt;
    &lt;span class="vi"&gt;@sales_tax_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sales_tax_rate&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total&lt;/span&gt;
    &lt;span class="n"&gt;discounted_amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;sales_tax&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discounted_amount&lt;/span&gt;
    &lt;span class="vi"&gt;@amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="vi"&gt;@discount_pct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sales_tax&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@sales_tax_rate&lt;/span&gt;
      &lt;span class="n"&gt;discounted_amount&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="vi"&gt;@sales_tax_rate&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks like one of the project maintainers renamed the &lt;code&gt;@tax_rate&lt;/code&gt; instance variable to &lt;code&gt;@sales_tax_rate&lt;/code&gt;. The monkeypatch checks the value of the old &lt;code&gt;@tax_rate&lt;/code&gt; variable, which is always &lt;code&gt;nil&lt;/code&gt;. Nobody noticed because no errors were ever raised. The app chugged along as if nothing had happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Monkeypatch?
&lt;/h2&gt;

&lt;p&gt;Given these examples, it might seem like monkeypatching just isn't worth the potential headaches. So why do we do it? In my opinion, there are three major use-cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To fix broken or incomplete 3rd-party code&lt;/li&gt;
&lt;li&gt;To quickly test a change or multiple changes in development&lt;/li&gt;
&lt;li&gt;To wrap existing functionality with instrumentation or annotation code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In some cases, the &lt;em&gt;only&lt;/em&gt; viable way to address a bug or performance issue in 3rd-party code is to apply a monkeypatch.&lt;/p&gt;

&lt;p&gt;But with great power comes great responsibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monkeypatching Responsibly
&lt;/h2&gt;

&lt;p&gt;I like to frame the monkeypatching conversation around responsibility instead of whether or not it's good or bad. Sure, monkeypatching can cause chaos when done poorly. However, if done with some care and diligence, there's no reason to avoid reaching for it when the situation warrants it.&lt;/p&gt;

&lt;p&gt;Here's the list of rules I try to follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrap the patch in a module with an obvious name and use &lt;code&gt;Module#prepend&lt;/code&gt; to apply it&lt;/li&gt;
&lt;li&gt;Make sure you're patching the right thing&lt;/li&gt;
&lt;li&gt;Limit the patch's surface area&lt;/li&gt;
&lt;li&gt;Give yourself escape hatches&lt;/li&gt;
&lt;li&gt;Over-communicate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the remainder of this article, we're going to use these rules to write up a monkeypatch for Rails' &lt;code&gt;DateTimeSelector&lt;/code&gt; so it optionally skips rendering discarded fields. This is a change I actually tried to make to Rails a few years ago. &lt;a href="https://github.com/rails/rails/pull/31533"&gt;You can find the details here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You don't have to know much about discarded fields to understand the monkeypatch, though. At the end of the day, all it does is replace a single method called &lt;code&gt;build_hidden&lt;/code&gt; with one that effectively does nothing.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;Module#prepend&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;In the codebase I encountered in my previous role, all the implementations of &lt;code&gt;String#%&lt;/code&gt; were applied by reopening the &lt;code&gt;String&lt;/code&gt; class. Here's an augmented list of the drawbacks I mentioned earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Errors appear to have originated from the host class or module instead of from the patch code&lt;/li&gt;
&lt;li&gt;Any methods you define in the patch replace existing methods with the same name, meaning, there's no way of invoking the original implementation.&lt;/li&gt;
&lt;li&gt;There's no way to know which patches were applied and therefore, which methods "won"&lt;/li&gt;
&lt;li&gt;Patches leave almost no audit trail, which makes them very difficult to find later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, it's much better to wrap your patch in a module and apply it using &lt;code&gt;Module#prepend&lt;/code&gt;. Doing so leaves you free to call the original implementation, and a quick call to &lt;code&gt;Module#ancestors&lt;/code&gt; will show the patch in the inheritance hierarchy so it's easier to find if things go wrong.&lt;/p&gt;

&lt;p&gt;Finally, a simple &lt;code&gt;prepend&lt;/code&gt; statement is easy to comment out if you need to disable the patch for some reason.&lt;/p&gt;

&lt;p&gt;Here are the beginnings of a module for our Rails monkeypatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;ActionView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Helpers&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DateTimeSelector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Patch the Right Thing
&lt;/h3&gt;

&lt;p&gt;If you take one thing away from this article, let it be this: don't apply a monkeypatch unless you know you're patching the right code. In most cases, it should be possible to verify programmatically that your assumptions still hold (this is Ruby after all). Here's a checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make sure the class or module you're trying to patch exists&lt;/li&gt;
&lt;li&gt;Make sure methods exist and have the right arity&lt;/li&gt;
&lt;li&gt;If the code you're patching lives in a gem, check the gem's version&lt;/li&gt;
&lt;li&gt;Bail out with a helpful error message if assumptions don't hold&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Right off the bat, our patch code has made a pretty important assumption. It assumes a constant called &lt;code&gt;ActionView::Helpers::DateTimeSelector&lt;/code&gt; exists and is a class or module.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check Class/Module
&lt;/h3&gt;

&lt;p&gt;Let's ensure that constant exists before trying to patch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;begin&lt;/span&gt;
  &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
  &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, but now we've leaked a local variable (&lt;code&gt;const&lt;/code&gt;) into the global scope. Let's fix that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
    &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check Methods
&lt;/h3&gt;

&lt;p&gt;Next, let's introduce the patched &lt;code&gt;build_hidden&lt;/code&gt; method. Let's also add a check to make sure it exists and accepts the right number of arguments (i.e. has the right arity). If those assumptions don't hold, something's probably wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_patch&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_const&lt;/span&gt;
      &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_const&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:build_hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s1"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check Gem Versions
&lt;/h3&gt;

&lt;p&gt;Finally, let's check that we're using the right version of Rails. If Rails gets upgraded, we might need to update the patch too (or get rid of it entirely).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_patch&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_const&lt;/span&gt;
      &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rails_version_ok?&lt;/span&gt;
        &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_const&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:build_hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rails_version_ok?&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAJOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MINOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s1"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bail Out Helpfully
&lt;/h3&gt;

&lt;p&gt;If your verification code uncovers a discrepancy between expectations and reality, it's a good idea to raise an error or at least print a helpful warning message. The idea here is to alert you and your co-workers when something seems amiss.&lt;/p&gt;

&lt;p&gt;Here's how we might modify our Rails patch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_patch&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_const&lt;/span&gt;
      &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Could not find class or method when patching "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_select helper. Please investigate."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;rails_version_ok?&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: It looks like Rails has been upgraded since "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_select helper was monkeypatched in "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Please reevaluate the patch."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_const&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:build_hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rails_version_ok?&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAJOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MINOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="s1"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limit Surface Area
&lt;/h3&gt;

&lt;p&gt;While it may seem perfectly innocuous to define helper methods in a monkeypatch, remember that any methods defined via &lt;code&gt;Module#prepend&lt;/code&gt; will override existing ones through the magic of inheritance. While it might seem as though a host class or module doesn't define a particular method, it's difficult to know for sure. For this reason, I try to only define methods I intend to patch.&lt;/p&gt;

&lt;p&gt;Note that this also applies to methods defined in the object's singleton class, i.e. methods defined inside &lt;code&gt;class &amp;lt;&amp;lt; self&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's how to modify our Rails patch to only replace the one &lt;code&gt;#build_hidden&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_patch&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_const&lt;/span&gt;
      &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Could not find class or method when patching"&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_select helper. Please investigate."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;rails_version_ok?&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: It looks like Rails has been upgraded since"&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_selet helper was monkeypatched in "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Please reevaluate the patch."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;InstanceMethods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_const&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:build_hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rails_version_ok?&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAJOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MINOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;InstanceMethods&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="s1"&gt;''&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Give Yourself Escape Hatches
&lt;/h3&gt;

&lt;p&gt;When possible, I like to make my monkeypatch's functionality opt-in. That's only really an option if you have control over where the patched code is invoked. In the case of our Rails patch, it's doable via the &lt;code&gt;@options&lt;/code&gt; hash in &lt;code&gt;DateTimeSelector&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_patch&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_const&lt;/span&gt;
      &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Could not find class or method when patching"&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_select helper. Please investigate."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;rails_version_ok?&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: It looks like Rails has been upgraded since"&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_selet helper was monkeypatched in "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Please reevaluate the patch."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;InstanceMethods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_const&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:build_hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rails_version_ok?&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAJOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MINOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;InstanceMethods&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:render_discarded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="s1"&gt;''&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice! Now callers can opt-in by calling the &lt;code&gt;date_select&lt;/code&gt; helper with the new option. No other codepaths are affected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;date_select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:date_of_birth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="ss"&gt;order: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:day&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;render_discarded: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Over-Communicate
&lt;/h3&gt;

&lt;p&gt;The last piece of advice I have for you is perhaps the most important — communicating what your patch does and when it's time to re-examine it. Your goal with monkeypatches should always be to eventually remove the patch altogether. To that end, a responsible monkeypatch includes comments that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Describe what the patch does&lt;/li&gt;
&lt;li&gt;Explain why the patch is necessary&lt;/li&gt;
&lt;li&gt;Outline the assumptions the patch makes&lt;/li&gt;
&lt;li&gt;Specify a date in the future when your team should reconsider alternative solutions, like pulling in an updated gem&lt;/li&gt;
&lt;li&gt;Include links to relevant pull requests, blog posts, StackOverflow answers, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might even print a warning or fail a test on a predetermined date to urge the team to reconfirm the patch's assumptions and consider whether or not it's still necessary.&lt;/p&gt;

&lt;p&gt;Here's the final version of our Rails &lt;code&gt;date_select&lt;/code&gt; patch, complete with comments and a date check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ActionView's date_select helper provides the option to "discard" certain&lt;/span&gt;
&lt;span class="c1"&gt;# fields. Discarded fields are (confusingly) still rendered to the page&lt;/span&gt;
&lt;span class="c1"&gt;# using hidden inputs, i.e. &amp;lt;input type="hidden" /&amp;gt;. This patch adds an&lt;/span&gt;
&lt;span class="c1"&gt;# additional option to the date_select helper that allows the caller to&lt;/span&gt;
&lt;span class="c1"&gt;# skip rendering the chosen fields altogether. For example, to render all&lt;/span&gt;
&lt;span class="c1"&gt;# but the year field, you might have this in one of your views:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# date_select(:date_of_birth, order: [:month, :day])&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# or, equivalently:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# date_select(:date_of_birth, discard_year: true)&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# To avoid rendering the year field altogether, set :render_discarded to&lt;/span&gt;
&lt;span class="c1"&gt;# false:&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# date_select(:date_of_birth, discard_year: true, render_discarded: false)&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# This patch assumes the #build_hidden method exists on&lt;/span&gt;
&lt;span class="c1"&gt;# ActionView::Helpers::DateTimeSelector and accepts two arguments.&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="no"&gt;EXPIRATION_DATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_patch&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EXPIRATION_DATE&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: Please re-evaluate whether or not the ActionView "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"date_select patch present in &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is still necessary."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_const&lt;/span&gt;
      &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# make sure the class we want to patch exists;&lt;/span&gt;
      &lt;span class="c1"&gt;# make sure the #build_hidden method exists and accepts exactly&lt;/span&gt;
      &lt;span class="c1"&gt;# two arguments&lt;/span&gt;
      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arity&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Could not find class or method when patching "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_select helper. Please investigate."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# if rails has been upgraded, make sure this patch is still&lt;/span&gt;
      &lt;span class="c1"&gt;# necessary&lt;/span&gt;
      &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;rails_version_ok?&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: It looks like Rails has been upgraded since "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"ActionView's date_select helper was monkeypatched in "&lt;/span&gt;&lt;span class="p"&gt;\&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Please re-evaluate the patch."&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# actually apply the patch&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;InstanceMethods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_const&lt;/span&gt;
      &lt;span class="no"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;const_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ActionView::Helpers::DateTimeSelector'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
      &lt;span class="c1"&gt;# return nil if the constant doesn't exist&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;const&lt;/span&gt;
      &lt;span class="n"&gt;const&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instance_method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:build_hidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;NameError&lt;/span&gt;
      &lt;span class="c1"&gt;# return nil if the method doesn't exist&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rails_version_ok?&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MAJOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MINOR&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;InstanceMethods&lt;/span&gt;
    &lt;span class="c1"&gt;# :render_discarded is an additional option you can pass to the&lt;/span&gt;
    &lt;span class="c1"&gt;# date_select helper in your views. Use it to avoid rendering&lt;/span&gt;
    &lt;span class="c1"&gt;# "discarded" fields, i.e. fields marked as discarded or simply&lt;/span&gt;
    &lt;span class="c1"&gt;# not included in date_select's :order array. For example,&lt;/span&gt;
    &lt;span class="c1"&gt;# specifying order: [:day, :month] will cause the helper to&lt;/span&gt;
    &lt;span class="c1"&gt;# "discard" the :year field. Discarding a field renders it as a&lt;/span&gt;
    &lt;span class="c1"&gt;# hidden input. Set :render_discarded to false to avoid rendering&lt;/span&gt;
    &lt;span class="c1"&gt;# it altogether.&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:render_discarded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="s1"&gt;''&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="no"&gt;RenderDiscardedMonkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_patch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I totally get that some of the suggestions I've outlined above might seem like overkill. Our Rails patch contains way more defensive verification code than actual patch code!&lt;/p&gt;

&lt;p&gt;Think of all that extra code as a sheath for your broadsword. It's a lot easier to avoid getting cut if it's enveloped in a layer of protection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/KFPRdKqy96oaI4HWEn/giphy-downsized.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/KFPRdKqy96oaI4HWEn/giphy-downsized.gif" alt="Sword guitar"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What really matters, though, is that I feel confident deploying responsible monkeypatches into production. Irresponsible ones are just time bombs waiting to cost you or your company time, money, and developer health.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Ruby Magic posts as soon as they get off the press, &lt;a href="https://blog.appsignal.com/#ruby-magic"&gt;subscribe to our Ruby Magic newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Cameron currently works on the Design Infrastructure team at GitHub. He's been programming in Ruby and using Rails for the better part of ten years. When he's not working with technology, Cameron can be found hiking around his neighborhood or hanging out at home with his wife, daughter, and cat.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
  </channel>
</rss>
