aboutsummaryrefslogtreecommitdiff
path: root/content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html
diff options
context:
space:
mode:
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.html143
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>&gt;&gt; strs = %w{monkey patch}
+?&gt; strs.map(&amp;:pluralize)
+=&gt; ["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
+=&gt; String
+nog_str.de_nog
+=&gt; "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
+
+&gt;&gt; a = A.new
+&gt;&gt; a.secret
+NoMethodError: private method `secret' called for #&lt;A:0xb78b9fb8&gt;
+ from (irb):40
+&gt;&gt; a.instance_eval { secret }
+=&gt; "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>
+
+