diff options
Diffstat (limited to 'content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html')
-rw-r--r-- | content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html b/content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html new file mode 100644 index 0000000..af40a6f --- /dev/null +++ b/content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html @@ -0,0 +1,143 @@ +--- +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> + + |