Ruby is a wonderful language. Despite the type wars between static and dynamic (typing/checking) or that some say it might be dying (which I don’t agree with), I just find Ruby a great language to learn. Its readable and has features that hooked me from the start. I never used it on a big project or anything professionally. But I learned enough of it to send some pull requests to Ruby projects on GitHub and also develop solutions for the advent of code.

A famous Ruby book called metaprogramming with ruby [1] walks through some concepts of metaprogramming (in Ruby, of couses) and presents some examples.

The first concept presented by this book is called Open Classes.

Open Classes definition

The concept of Open Classes is used as a metaprogramming attribute to change an object’s behavior in runtime. In Ruby there is no real distinction between code that defines a class and code of any other kind [1], therefore, any code that is put within a class block is processed like any other piece of code.

So how is a class defined?

Whenever I use the keyword class, this class is opened for modifications, however if this class was not yet modified, Ruby will take care of that for you.

Drawbacks

Open class can be convenient most times, but given the dynamic nature of Ruby language and such feature, you might run into some problems.

Duplicate method definition

Imagine you use this feature on standard Array class. You have a method that replaces a handful of values in a list according to your program’s logic.

class Array
  def replace (x, y, z)
    # ...
  end
end

Unfortunately, this breaks a lot of other functionalities, because Array#replace is already defined in the standard ruby library. And, lucky for you, Ruby will not tell you, the way Open Class works, it will simply re-open Array class and overwrite current replace method.

We can see array has a replace method running:

[].methods.grep /replace/ # => #[:replace]

This is only an example, there are several drawbacks and advantages to Monkey-Patching. The bottom line is that you must use it safely and rarely. There are a lot of resources online explaining Monkey-Patching and its quirks [3][4].

Money Example

Moving on, the book [1] brings an example from the money gem where the Open Class principle was used. The example is as follows:

class Numeric
  def to_money
    Money.new(self * 100)
  end
end

# 100.to_money => #<Money @cents=10000>

I wanted to follow along or at least have some sort of a deep dive on this, so I joined in and cloned the source code for the money gem and went on looking for the Numeric class Monkey-Patch[2]. However, this snippet of code was no longer present. All the Open Class shenanigans, along with a handful of new String methods vanished.

I started hunting for this feature on their changelog and past releases.

What I figured out during this process:

  • After v4.0.0 they changed the heavy logic on String patch to static calls to a parse method.
  • Until version 5.0.0 the Monkey-Patch was still in place
  • On v6.0.0 they added a deprecated flag and only after v6.1.0 they removed it completely

What really happend was: they moved the code to another place. Where? Here. Another repository, they isolated the logic as an addon of the Money gem.

Now, how do I feel about this? I mostly like this approach because of the following points:

Your application’s public API

When you change the behavior of a standard Ruby class (e.g. String or Numeric), you are defining part of the public API of your application. Since it is not the key feature of this gem, it’s just a handy helper method to create a Money type object, it makes sense to provide this as an addon to your users to use at their own risk.

Ease the conflicts

If another library patches the same methods/classes you can simply don’t plug the Monetize gem and live happily without its Monkey-Patching.

Conclusion

Well, Open Class is a nice concept from Ruby. You can use it to Monkey-Patch classes and twist and turn standard Ruby library all you want. And it’s fun to do so, it is one of the things that gives Ruby that special look. I also enjoy going hunting commits and understanding design decisions on Open Source projects based on their development history. I recommend you to do it from time to time it’s super relaxing and not frustrating at all.

References

[1]: Perrotta, P. (2010). Metaprogramming Ruby. Pragmatic Bookshelf.

[2]: Monkey-Patch is a term used when you change methods of a class during runtime, specially (in Ruby) from a standard Ruby class (e.g. String, List, Numeric…)

[3]: What is monkey patching

[4]: 3 Ways to Monkey-Patch Without Making a Mess