--- date: "2006-12-15T06:34:07Z" title: 'Hijacking a Pejorative: Monkey Patching and Technorati-Ruby' --- <p></p> <p><img src='http://pablotron.org/files/monkey_patch.jpg' width='200' height='163' title='Monkey Patch' alt='Monkey Patch' align='right' border='0' style='padding: 5px;' /></p> <p>The new version of <a href="http://pablotron.org/software/technorati-ruby/">Technorati-Ruby</a> adds a bit of magic to return values. Version 0.1.0 returns standard <a href="http://ruby-lang.org/">Ruby</a> hashes. A list of items in the returned value -- blogs from <code>Technorati#cosmos</code> or tags from <code>Technorati#tag</code>, for example -- are returned as an array of hashes under the the <code>'items'</code> key, like so:</p> <pre><code># find sites linking that link to me results = tr.cosmos('pablotron.org') # print an excerpt from each item puts results['items'].map { |item| [item['url'], item['excerpt']] } </code></pre> <p>It's a simple system, and using a hash instead of a pre-defined class reinforces the idea that the return values could be unavailable, change, or possibly even be removed. The problem, of course, is that the hash references in the example above clutter the code and cause it to look more like [Perl] than Ruby.</p> <p>I wanted to give the results a bit more of a Ruby feel, preferrably without breaking backwards compatability. I came up with a solution that I'm pretty happy with. We'll get to that in a minute; first let's talk about <em>monkey patching</em>.</p> <p><strong>Monkey Patching</strong><br/> What is monkey patching, anyway? <a href="http://en.wikipedia.org/wiki/Monkey_patch">Wikipedia defines it</a> as "a way to extend or modify runtime code without altering the original source code for dynamic languages". If you're a <a href="http://rubyonrails.com/">Rails</a> user, you've already been merrily enjoying the benefits of monkey patching:</p> <pre><code>>> strs = %w{monkey patch} ?> strs.map(&:pluralize) => ["monkeys", "patches"]` </code></pre> <p>(Hint: neither <code>String#pluralize</code> nor the no block/one-argument form of <code>Enumerable#map</code> exist in the standard library; both are grafted on at run-time by ActiveSupport)</p> <p>Anyway, the <a href="http://python.org/">Python</a> community <a href="http://blog.ianbicking.org/theres-so-much-more-than-rails.html">frowns on the practice</a>. In fact, the term "monkey patch" comes from the Python community, and is actually meant as a pejorative. The Ruby community, on the other hand, is more tolerant of the practice. <a href="http://chadfowler.com/">Chad's</a> post, <a href="http://chadfowler.com/index.cgi/Computing/Programming/Ruby/TheVirtuesOfMonkeyPatching.rdoc,v">"The Virtues of Monkey Patching"</a>, is a fantastic real-world example of how monkey patching can be beneficial. When is monkey patching appropriate, and when should it be avoided? Here's my rule of thumb:</p> <blockquote> <p><strong>Paul's Rule of Monkey Patching</strong><br/> <em>Libraries should not modify underlying classes at runtime unless that is their express purpose and applications should ignore what I just said.</em></p> </blockquote> <p>How does monkey patching apply to Technorati-Ruby? Well, it doesn't, or at least not directly. I didn't want to extend the standard library for little old Technorati-Ruby, and I didn't really want to sub-class <code>Hash</code> either. Fortunately, I had another option: <a href="http://project.ioni.st/post/966#post-966">just in time convenience methods</a>, the sneaky and verbosely-named cousin of monkey patching. </p> <p><strong>Just in Time Convenience Methods</strong><br/> A <em>just in time convenience method</em> is a convenience method that is added to an <em>instance</em> of a class, rather than the class itself. <a href="http://weblog.jamisbuck.org/2006/11/15/mini-api-s">Jamis</a> and <a href="http://project.ioni.st/post/966#post-966">Marcel</a> both have more to say about them, but here's what they look like:</p> <pre><code>nog_str = 'delicious egg nog' def nog_str.de_nog gsub!(/egg nog/i, 'apple juice') end nog_str.class => String nog_str.de_nog => "delicious apple juice" </code></pre> <p>As you can see, <code>nog_str</code> is still a <code>String</code>, just with a little more personality. You can also use <code>Object.instance_eval</code>:</p> <pre><code>class A private def secret 'secret message' end end >> a = A.new >> a.secret NoMethodError: private method `secret' called for #<A:0xb78b9fb8> from (irb):40 >> a.instance_eval { secret } => "secret message" </code></pre> <p>Which brings us back to Technorati-Ruby. First, I added a bit of code to jazz up result hashes with convenience methods:</p> <pre><code>def magify_hash(hash) hash.keys.each do |key| meth_key = key.gsub(/\//, '_') hash.instance_eval %{def #{meth_key}; self['#{key}']; end} end hash end </code></pre> <p>Second, I wrapped the result hashes in <code>magify_hash</code>. Third? There wasn't really a third step, so I just sat around for a few minutes feeling smug. By the way, here's that example code from the beginning, updated to use the shiny new convenience methods:</p> <pre><code># find sites linking that link to me results = tr.cosmos('pablotron.org') # print an excerpt from each item puts results.items.map { |item| [item.url, item.excerpt] } </code></pre> <p>So, problem solved. Just in time convenience methods satisfy all my requirements: they're backwards compatible, don't require me to create a new class or sub-class <code>Hash</code>, and they allow users to write cleaner code. Not bad for a sneaky pejorative.</p>