January 16th, 2011
Ruby make it possible to pretty much change anything, anywhere. This is obviously very powerful, but it’s also something that can cause a lot of pain if it’s not done in a disciplined manner. The way this is handled on most Ruby projects is by heaving clear strategies for what to change, how to name it and where to put the source file. The most basic advice is to always use modules for extensions and changes if it is at all possible. There are several good reasons for this, but the main one is that it makes it easier for someone debugging your application to find out where the code is defined.
The one absolute rule that should never be violated in a Rails or Ruby project is to modify the original source code. In the worst case, fork the project and make the changes there, but never, never, never change code in vendor/plugins or vendor/gems.
Let’s start with a simple example. Say I want to recreate the presence method I mentioned in a previous blog post. A first version make look like this:
class Object def presence return self if present? end end
But if I open up IRb and get hold of this method, it’s not immediately obvious where it’s defined:
o = Object.new p o.method(:presence) #=> #<Method: Object#presence>
However, if I were to implement it using a module instead, like this:
module Presence def presence return self if present? end end Object.send :include, Presence
If I look at the method now, the output is a bit changed:
p o.method(:presence) #=> #<Method: Object(Presence)#presence>
We can now see that the method actually comes from the Presence module instead of the Object class. In most Ruby projects, these kind of extensions will be namespaced, using the word extensions or ext as part of the module name. When I add the presence method to code bases, I usually put it in lib/core_ext/object/presence.rb, in a module called CoreExt::Object::Presence. All of this to make it as easy to possible to find these extensions and changes.
There are many other benefits to putting an extension like this in a module. It makes your code cleaner, more flexible, and it composes better if you happen to have conflicting definitions. You can also use modules more selectively if you want, including just adding it to selected objects if necessary.
Props to my colleague Brian Guthrie for alerting me to this useful side effect of defining extensions with modules.
There is a slight wrinkle in this scenario, specifically for adding extensions to modules. Sadly, the way the Ruby module system works, you can’t include a new module into Enumerable and have that take effect in places where Enumerable has already been mixed in. Instead you have to define the methods directly on Enumerable. The general problem looks like this:
module X def hello 42 end end class Foo include X end Foo.new.hello #=> 42 module Y def goodbye 25 end end module X include Y end Foo.new.goodbye #=> undefined method `goodbye' for #<Foo:0x129f94> (NoMethodError)
This is a bit sad, since it means extensions have to be written in two different ways, depending on where you aim to use them. The general rules still applies — you should put the extensions in well named files that are easy to find. And if you can extract the functionality to a module and then delegate to that, that is preferrable.