[Ruby] Blocks as Resource Management
Posted: Thu, 8 June 2006 | permalink | No comments
One of the really neat things about Ruby is the extensive use of blocks (closures, I think the rest of the world calls them) in the standard library to control the allocation and deallocation of resources. Take the following snippet:
File.open('something.txt') do |fd| # Manipulate the file through 'fd' end # File handle is now closed
There's no way that you can escape from that block without the file being closed (well, in theory, you could lose the file handle if there was an exception thrown in the block, but the File::open method can take care of that internally by catching, closing, then rethrowing).
I've loved this feature since I first saw it -- "holy crap!" I thought to myself, "I never have to call close again!". And I was pretty well right -- I think I can count (in unary, not binary!) on one hand (well, I'd use grep | wc) the number of times I've used IO#close in my Ruby code.
The funny thing is, though, that I don't think I've used this pattern in my own code enough. But I just had an epiphany whilst waiting for a train home -- and I think I'm cured. Take this hideous piece of test code I had laying around in an app I'm working on at the moment:
def test_something_funny faux_path_on d = Domain.new('127.0.0.1', 'somethingfunny.com') faux_path_off # assert writ large end
Domain::new calls out to a shell command (dig(1) to be precise -- take a guess at what I'm writing <grin>, and yes, it will be released shortly) but for test purposes I can't run dig (because I'm not guaranteed to have all the infrastructure available for a real dig to succeed). So I have a flimsy mock of dig in a directory in my test suite, which is where faux_path_on sets ENV['PATH'] to look at.
This is, of course, pretty untidy, because when (not if!) I forget to call faux_path_off, everything goes to complete poop, and my children will have two heads or something.
The much cleaner version is like this:
def test_something_funny d = faux_path { Domain.new } # Assert ahoy! end
And my faux_path method (I've even saved a method!) is as simple as simple can be:
def faux_path realpath = ENV['PATH'] ENV['PATH'] = my_fake_path rv = yield ENV['PATH'] = realpath return rv end
I also have a trickier faux_* method, which sets a couple of environment variables which tell my mock nsupdate(8) where to write the data it gets sent from my application -- again, it's a block-taking method that sets the environment, yields, reads in the data from the temporary file, cleans it up, and returns the contents of the aforementioned file.
The power of the Ruby is growing within me, I can feel it. And I like it.
Post a comment
All comments are held for moderation; markdown formatting accepted.