From 4b6c0e31385f5f27a151088c0a2b614495c4e589 Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Thu, 14 Oct 2021 12:47:50 -0400 Subject: initial commit, including theme --- ...rative-monkey-patching-and-technorati-ruby.html | 143 +++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html (limited to 'content/posts/2006-12-15-hijacking-a-pejorative-monkey-patching-and-technorati-ruby.html') 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' +--- + +

+ +

Monkey Patch

+ +

The new version of Technorati-Ruby adds a bit of magic to return +values. Version 0.1.0 returns standard Ruby hashes. A list of +items in the returned value -- blogs from Technorati#cosmos or tags +from Technorati#tag, for example -- are returned as an array of hashes +under the the 'items' key, like so:

+ +
# 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']]
+}
+
+ +

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.

+ +

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 monkey patching.

+ +

Monkey Patching
+What is monkey patching, anyway? Wikipedia defines it as "a +way to extend or modify runtime code without altering the original +source code for dynamic languages". If you're a Rails user, you've +already been merrily enjoying the benefits of monkey patching:

+ +
>> strs = %w{monkey patch}
+?> strs.map(&:pluralize)
+=> ["monkeys", "patches"]`
+
+ +

(Hint: neither String#pluralize nor the no block/one-argument form of +Enumerable#map exist in the standard library; both are grafted on at +run-time by ActiveSupport)

+ +

Anyway, the Python community frowns on the practice. 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. Chad's post, "The Virtues of +Monkey Patching", 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:

+ +
+

Paul's Rule of Monkey Patching
+ Libraries should not modify underlying classes at runtime unless that + is their express purpose and applications should ignore what I just + said.

+
+ +

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 Hash +either. Fortunately, I had another option: just in time convenience +methods, the sneaky and verbosely-named cousin of monkey +patching.

+ +

Just in Time Convenience Methods
+A just in time convenience method is a convenience method that is +added to an instance of a class, rather than the class itself. +Jamis and Marcel both have more to say about them, but here's +what they look like:

+ +
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"
+
+ +

As you can see, nog_str is still a String, just with a little more +personality. You can also use Object.instance_eval:

+ +
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"
+
+ +

Which brings us back to Technorati-Ruby. First, I added a bit of code +to jazz up result hashes with convenience methods:

+ +
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
+
+ +

Second, I wrapped the result hashes in magify_hash. 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:

+ +
# 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] 
+}
+
+ +

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 Hash, and they allow users to write cleaner +code. Not bad for a sneaky pejorative.

+ + -- cgit v1.2.3