Sporkmonger

purveyor of fabulously ambiguous eating utensils

HOWTO: Monkey Patch Without Annoying Others

Posted by sporkmonger
Written July 8th, 2006

It’s really very, very simple. Subclass first, then monkey patch the subclass and use that instead.

Update: Here’s an example, from one of the most over-monkey-patched classes ever:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

require 'logger'
require File.dirname(__FILE__) + '/core_ext/class/attribute_accessors'

class CleanLogger < Logger #:nodoc:
  cattr_accessor :silencer
  self.silencer = true

  # Silences the logger for the duration of the block.
  def silence(temporary_level = Logger::ERROR)
    if silencer
      begin
        old_logger_level, self.level = level, temporary_level
        yield self
      ensure
        self.level = old_logger_level
      end
    else
      yield self
    end
  end

  private
    alias old_format_message format_message

    # Ruby 1.8.3 transposed the msg and progname arguments to format_message.
    # We can't test RUBY_VERSION because some distributions don't keep Ruby
    # and its standard library in sync, leading to installations of Ruby 1.8.2
    # with Logger from 1.8.3 and vice versa.
    if method_defined?(:formatter=)
      def format_message(severity, timestamp, progname, msg)
        "#{msg}\n"
      end
    else
      def format_message(severity, timestamp, msg, progname)
        "#{msg}\n"
      end
    end
end

Notice the difference? See, that wasn’t so bad now, was it?

Update: I’m certainly not the first to complain about this.

  1. teh n00b teh n00b :
    Written July 9th, 2006 at 09:37 AM

    As a n00b I can say that example code would be useful.

  2. josh josh :
    Written July 9th, 2006 at 05:45 PM

    I don’t get it. “patching” a subclass has no effect on the superclass. I agree that subclassing is best if you just want to extend a class’s behavior for your own use (how is that “monkey patching”?). But when you want to modify something a class does globally, you need to patch the class itself.

  3. teh n00b teh n00b :
    Written July 9th, 2006 at 06:16 PM

    Bob,

    Sweet thanks for the example.

    Josh,

    From what I understood from the title, this is about patching functionality that might break other code if you had it take effect globally. I actually have this situation and have been overriding inside my controllers which I dislike.

  4. Written July 9th, 2006 at 07:06 PM

    josh: What teh n00b said. There are very, very, very few cases where modifying a function’s behavior globally is the correct way of going about things. Which, I imagine, is largely why the Python folks tend to frown on the practice. The only reason I can think of why you would do that is if the function is, in fact, producing incorrect results. And if you apply a monkey patch in that case, people aren’t likely to be annoyed by their broken code suddenly working. However, if you monkey patch when you should have subclassed, like the Rails folks did here, that can get annoying really quick. I’ve personally run afoul of their Logger monkey patch several times now since I have a couple of libraries and apps that use ActiveRecord, but not the rest of Rails. As soon as ActiveRecord is loaded, the logger stops working as expected. I had to subclass and remonkey patch it back to the way it was in one instance.

  5. Written July 10th, 2006 at 06:59 PM

    Additional clarification:

    Monkey patching to add new functionality is fine. Changing existing behavior, generally, is not. If you’re going to change behavior, do it in a subclass if at all possible.

Leave a Response

NOTE: I'm afraid Javascript needs to be on in order to comment.

Comments should be formatted using Textile.

Ruby code should be enclosed within a <macro:code lang="ruby"> element. Other languages are supported. For output you can simply omit the lang attribute.