Little Things: Refactoring with Hashes
Ruby owes its popularity to an abundance of “little things”–small touches in just the right places that make all of its features come together in delightful ways. One of those little things, for me, is the humble
Hashes are one of the most versatile data structures in Ruby. I’ve written about them before, regarding their interchangability with
Proc objects, but there’s so much more they can do.
I was recently doing a code review and encountered a pattern where user input was being used to derive a class to instantiate. It looked something like this. (Note that this is not the actual code, and is not even the same use-case. I’ve taken some liberties here.)
I have no doubt that many of you reading this will take one look at that code and think of half a dozen ways in which it might be refactored. (In fact, I was a bit reluctant to write this article, for fear that folks might bike-shed over the best way to refactor my example here. I did it anyway. :))
Looking at the code, my first impression was that the case statement was merely selecting a different class based on the value of the user input. We could easily refactor it (for a slight improvement in clarity) like this:
Right? And when I see a case statement being used simply to select between different values given some input, I find myself itching to rewrite it using a hash. Because, really, what is a hash, except a mapping that selects between different values, given some input?
This is the pattern I proposed in the code review:
It takes advantage of
Hash.new(default_value) to ensure that
Student::Unregistered is always what we get for any unrecognized input, and then
Hash#merge adds in the specific mappings.
The beauty of this, to me, is that the class-selection logic is now separate from the class-instantiation logic. The mapping itself can be declared outside the method, reducing clutter. That leaves us with just the two lines in the method itself: fetching the class to instantiate, and instantiating it. Easy to read, easy to test, and easy to maintain.
Win, win, and win!