Safe(r) monkey patching


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.


9 Comments, Comment or Ping

  1. Jeremy Gailor

    Great advice.

    January 16th, 2011

  2. Luikore

    To see where the method is defined, just use

    Method#source_location

    January 17th, 2011

  3. Luikore: Great advice, although it only works on 1.9.

    January 17th, 2011

  4. Adriano Mitre

    The gem retroactive_module_inclusion makes it possible to circumvent the wrinkle, so that

    module CoreExt
    module Enumerable
    module Stats
    def mean
    inject(&:+) / count.to_f
    end
    end
    end
    end
    Enumerable.retroactively_include CoreExt::Enumerable::Stats

    suffices to make
    (1..2).mean #=> 1.5
    and
    [1, 2].mean #=> 1.5
    work.

    By the way, the gem itself follows the namespace convetion suggested in the post.

    January 24th, 2011

  5. Adriano Mitre

    The gem retroactive_module_inclusion makes it possible to circumvent the wrinkle, so that

    module CoreExt
    module Enumerable
    module Stats
    def mean
    inject(&:+) / count.to_f
    end
    end
    end
    end
    Enumerable.retroactively_include CoreExt::Enumerable::Stats

    suffices to make

    (1..2).mean #=> 1.5

    and

    [1, 2].mean #=> 1.5

    work.

    By the way, the gem itself follows the namespace convetion suggested in the post.

    January 24th, 2011

  6. Adriano Mitre

    Sorry for reposting, I was trying to achieve proper indenting and syntax highlighting and there is no preview nor delete options… Anyway, sorry once again and feel free to edit to make it fit. Thank you!

    January 24th, 2011

  7. funny thing but i had to change the source code of gems in vendor plugins at one place , because the servers of the client had ruby 1.8.7 and there is absolutely no way they are going to chnage or upgrade because several other previously developed rails app were also running on the server, so i had run a rails 3 app which used some gems that had a hash : shortcut symbol instaed of => “hash rockets” this caused trouble with 1.8.7 as it didn’t recognize the shorthand for hash.

    May 4th, 2013

  1. Safe(er) monkey patching - January 16, 2011

Reply to “Safe(r) monkey patching”