Reopening Ruby Classes
In Ruby the implementation of a class is not closed. In Ruby, it is incredibly easy to add new methods to any existing class at runtime, even core system classes like String, Array, and Fixum. You just reopen a class and add new code. This language feature makes the language incredibly flexible. You might be wondering, why would you need this? Well, take a look at the API documentation of the Java String class. In most development organizations that I have been a part of we have had to write small string libraries that provide far more functionality that provided by the Java String class. The Apache Jakarta Commons Lang project provides a String helper class in StringUtils. StringUtils provides methods such as IsEmpty, Chomp/Chop, and LeftPad. StringUtils. StringUtils is a great class that has saved me from re-inventing the wheel several times, but it is another library to download, configure, learn, and import. Using the StringUtils class is more verbose calling a method on a string.
// Using StringUtils to trim a string. str = StringUtils.trim(str); // Calling a trim on a string. str = str.trim();
Isn’t more concise and easier to read when you invoke a method on a string instead of using StringUtils? Unfortunately, even if an JCP is approved to add your custom string methods to the String class, the whole process is very time consuming. Each incremental release of Java takes years to develop. In Ruby, you can just reopen the String class to add the few methods you feel it lacks. To reopen the Ruby String class we can use the following code:
# reopen the class String.class_eval do # define new method def get_size # method implementation length end end
You can also re-define any method in a given class, without subclassing, just by reopening the class. A mischevious example of this is to re-define the length method to always return 10, just because I couldn’t come up with a better example.
String.class_eval do def length 10 end end
This mischievous example, is meant to to make the point that the last implementation wins. The same method can be implemented in a class two or more times. The method that is valid is the one defined last. There is no compiler error or even a warning in Ruby if you duplicate a method in a class.
Perhaps in Perl fashion, Ruby provides more than one way to reopen a class. There is another more familiar way to open a class’ implementation, just define it again!
class String def get_size length end def length 10 end end
By looking at the above example I would think that it defines a new String class with just two methods. Ruby is a little smarter than that. Ruby will realized that we already have a String class defined, and it will open the class’ implementation so that we could add the new implementation with the existing class. It is important to reiterate that the above code will not create a new String class, it will just aggregate the new implementation to the existing String class.
The class_eval way of reopening a class comes in handy when extending the class of an object at runtime. Here is a trivial example, lets say we want to add a to_string method to the class of any given object instance. To accomplish this we would need to get the class of the object instance and invoke the class_eval method, like this:
def add_method(obj) # Get the class of the object. obj.class.class_eval do # Add a new method to the class. def to_string to_s end end end add_method(int_value) puts int_value.to_string
In the above example, we open the class of the object passed in as a parameter by calling class.class_eval.
In Java, the best way to enhance a class is to extend it through a subclass, but subclass are tightly coupled to the implementor class and come with a associated price. Ruby just seems to have a few more options to enhanced a class, such as mixins and as we have now seen here. It is possible to do byte code manipulation on a Java class to change its behavior after the class has been compiled, in fact Aspect Oriented Programming does this in a way. But Ruby’s way to enhanced a class is so seamless, powerful, and built right into the language itself.