You’ve probably been told that in Ruby classes are always open and any code can come along and redefine your methods in any way it pleases. Indeed, this is true.
What’s not true, despite every reference I’ve read saying the contrary, is that you can’t prevent this from happening.
Ruby provides a
method_added() callback that is invoked every time a method is added or redefined within a class. It’s part of the
Module class, and every
Class is a
Module. There are also two related callbacks called
This means you could detect when other code has redefined a method, and do something about it! How about redefining that method (again) to point back to your original code? Indeed, this works.
I’ve encapsulated the details of this in a new module I call
Immutable. It provides one class method called
immutable_method(). Provide it a list of methods you don’t want touched and it’ll make sure they can’t be redefined. Hence, immutable.
It’s on GitHub:
Here’s an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
:baz, even though we redefined
foo() to return
If this makes you think of Java’s
final method modifier, you’d be almost right in saying
immutable_method is similar. But there’s one important difference: with
immutable_method you can still redefine methods in subclasses. This makes sense, as one can argue that if you want to reimplement or extend a method, a child class is the only place where you should be doing that anyway. I tend to agree, call me a purist.
Why would this be useful to you? I don’t know. But I can tell you what motivated me.
My motivation for writing something that provides what some may call “evil” functionality (heh, and monkey patching isn’t evil?) of closed methods/classes came from my trials of trying to improve Rails loading time by reimplementing some ActiveSupport methods in C (Ruby extension).
I absolutely needed my C versions of certain methods to be present, and not clobbered by ActiveSupport.
So why didn’t I just load my extension after ActiveSupport? Because by then it is too late. Rails is already loaded, along with slow running methods that get called over 10,000 times during initialization.
Run the profiler and see for yourself:
This is a prelude to my next post, when I present the plugin I’ve built that takes away over 30% of overhead from Rails loading time. Stay tuned (or subscribe :)
But for now, if you want to try
Immutable, clone it from GitHub, or if you have RubyGems 1.2.0 or higher, do this:
1 2 3 4
Then you can run the example above. You’re all set.
See the README for further details.
Enjoy (or flame me for ruining your monkey patching).