Tags ruby Date

Safer Monkey Patching in Ruby

At 8th Light, my team and I are rigorously working on our most important client’s most important project: a command line Battleship gem, called battle_boats. \s

I recently sat down with my mentors and demo-ed version 0.0.4, where I added a bit of color in dev mode, and my mentors-as-stakeholders liked it so much, they’ve asked for more color! But how??

“It should be easy,” I thought. I already had some implementation ideas in mind thanks to this SO answer detailing how to open up the String class to add color methods that useANSI codes.

It goes something like this:

class String
  def blue
    "\e[34m#{self}\e[0m"
  end

  def red
    "\e[31m#{self}\e[0m"
  end

  def yellow
    "\e[33m#{self}\e[0m"
  end
end

So, we open up the String class and use the ANSI color codes to output our strings in color in ANSI-supported terminals.

This allows us to do this:

puts "Hello, World!".blue
#=> Hello, World! (in blue)

So What’s The Problem?

Being that this product is a Ruby gem, everything is namespaced under the module BattleBoats, so when I tried implementing this, it looked like this:

module BattleBoats
  class String
    # implementation
  end
end

I was surprised that this didn’t work, but I soon realized that in this instance, we’re not opening String, we’re defining BattleBoats::String. Not the same thing, and not what we want.

The fix is easy, right? Just don’t wrap it in the BattleBoats module. But that left me feeling antsy. There was something about polluting the String class in a global scope that didn’t sit right with me; it felt like an irresponsible use of metaprogramming.

Defining Pure Functions

I could always just define a new module and pass strings into its methods:

module BattleBoats
  module Colorize
    def blue(string)
      "\e[34m#{string}\e[0m"
    end
  end
end

This implementation just requires that we pass our string into our method to get the encoding we need to output in color:

include BattleBoats::Colorize
puts blue("Hello, World!")
#=> Hello, World! (in blue)

But this just seemed less fun. I have a chance to use monkey patching here, and you should always take advantage of every opportunity to use monkey patching!! /s

Using instance_eval

Another option I could think of was to define the color methods only on the instances of String that I need them. For example,

module BattleBoats
  def colorify(string)
    string.instance_eval do
      def blue(string)
        "\e[34m#{self}\e[0m"
      end
    end
  end
end

This implementation gives us the best of both worlds. 1) We get to call methods directly on the strings we want to colorize, and 2) we don’t effect any other String instances. It does have the downside of requiring an extra step, but no more than our previous approach:

include BattleBoats
puts colorify("Hello, World").blue
#=> Hello, World! (in blue)

This has the added advantage of allowing us to easily add new colors to the colorify‘d strings.


None of these implementations are bad, but I really really wished for a way to open up the String class only when I included the module where the actual methods were defined…

puts "Hello, World".blue
#=> NoMethodError: undefined method 'blue' for "Hello, World!":String

include Colorize
puts "Hello, World!".blue
#=> Hello, World! (in blue)

It turns out there is a metaprogramming technique to do exactly that!

Introducing Refinements

Due to Ruby’s open classes you can redefine or add functionality to existing classes. This is called a “monkey patch”. Unfortunately the scope of such changes is global. All users of the monkey-patched class see the same changes. This can cause unintended side-effects or breakage of programs.

Refinements are designed to reduce the impact of monkey patching on other users of the monkey-patched class. Refinements provide a way to extend a class locally.

Ruby Core Docs


It’s exactly what I was looking for! Scoped monkey patching!

module BattleBoats
  module Colorize
    refine String do
      def blue(string)
        "\e[34m#{string}\e[0m"
      end
    end
  end
end

We use it like this:

puts "Hello, World".blue
#=> NoMethodError: undefined method 'blue' for "Hello, World!":String

using Colorize
puts "Hello, World!".blue
#=> Hello, World! (in blue)

Where the keyword for introducing the refinement module is using rather than include.

Now I can keep my Colorize methods away from the String’s in the rest of the system and keep my conscious clear that I won’t introduce unexpected behavior in our very important client’s software!


Jokes aside, my exposure to metaprogramming and monkey patching has been fraught with warnings, so I’ve never really investigated the power it gives developers to write expressive code. Of course there are other ways of solving this problem, but given the opportunity to make safe use of this technique was just too exciting to pass up.

· ruby