Ruby: Refactoring from hash to object
Something I’ve noticed when I play around with Ruby in my own time is that I nearly always end up with the situation where I’m passing hashes all over my code and to start with it’s not a big deal.
Unfortunately I eventually get to the stage where I’m effectively modelling an object inside a hash and it all gets very difficult to understand.
I’ve written a few times before about incrementally refactoring code so this seemed like a pretty good chance for me to try that out.
The code in the view looked something like this:
<% @tweets.each do |tweet| %> <%= tweet[:key] %> <%= tweet[:value][:something_else] %> <% end %>
@tweets was being populated directly from a call to CouchDB so to start with I needed to change it from being a collection of hashes to a collection of objects:
I changed the Sinatra calling code from:
get '/' do @tweets = get_the_couchdb_tweets_hash end
to:
get '/' do tweets_hash = get_the_couchdb_tweets_hash @tweets = tweets_hash.map { |tweet| TweetViewModel.new(tweet) } end
where TweetViewModel is defined like so:
class TweetViewModel attr_accessor :key, :value def initialize(tweet_hash) @key = tweet_hash[:key] @value = tweet_hash[:value] end def get(lookup) if lookup == :key key else value end end alias_method :[], :get end
The next step was to get rid of the get method and rename those attr_accessor methods to something more intention revealing.
class TweetViewModel attr_accessor :url, :messages def initialize(tweet_hash) @url = tweet_hash[:key] @messages = tweet_hash[:value] end end
<% @tweets.each do |tweet| %> <%= tweet.url %> <%= tweet.messages[:something_else] %> <% end %>
I originally didn’t realise how easy it would be to make the TweetViewModel pretend to temporarily be a Hash but it actually made it really easy for me to change the code and know that it was working the whole way.
For someone with more Ruby experience perhaps it wouldn’t be necessary to break out the refactoring like this because they could fairly confidently do it in one go.