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 aparse
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…)
If you hated this post, and can't keep it to yourself, consider sending me an e-mail at fred.rbittencourt@gmail.com or complain with me at Twitter X @derfrb. I'm also occasionally responsive to positive comments.