Friday, August 31, 2007

Ruby: Adding a "not" method for readability

The other day I was working with an if statement that looked similar to the snippet below.

if !response.incomplete? && !response.invalid? && response.total > 0
...
end

I spent enough time in C, C#, etc to be able to parse the if statement fairly easily; however, I thought it would improve readability if I could write the following snippet instead.

if response.not.incomplete? && response.not.invalid? && response.total > 0
...
end

Adding a "not" method to Object turned out to be fairly easy. I started with the following tests (block syntax provided by dust).

require 'rubygems'
require 'test/unit'
require 'dust'

unit_tests do
test "not negates true to false" do
assert_equal false, nil.not.nil?
end

test "not negates false to true" do
assert_equal true, Object.new.not.nil?
end
end

The implementation of the "not" method is similar to the implementation of the "as" method provided by facets (an explanation of the "as" implementation can be found in a previous entry).

class Object
define_method :not do
Not.new(self)
end

class Not
private *instance_methods.select { |m| m !~ /(^__|^\W|^binding$)/ }

def initialize(subject)
@subject = subject
end

def method_missing(sym, *args, &blk)
!@subject.send(sym,*args,&blk)
end
end
end

The Object.not method returns an instance of Object::Not. The Object::Not instance is initialized with the subject (the instance that was sent the "not" message). The Object::Not instances are basically proxies that send all calls back to the original instance. The Object::Not class privatizes almost all of it's methods so that most method calls will be handled by method_missing. The method_missing implementation simply forwards on any method call to the subject and negates the result.
Post a Comment