Caching using nested hashes in Ruby

Rails 3 was a huge leap compared to previous versions. And one of the new features that was included roughly a year ago is ActionView::Resolver. In short, what it allows us to do is to incorporate elements in the logic that look for templates outside of the standard “views” directory. In fact, we can store them wherever we want as long as an object that finds them is provided, and this object has to comply with the Resolver API. That is not the topic for today’s post.

Such an interesting functionality should be tested, so I have written SQLTemplater, a non-production ready plugin that allows to store views or templates in a SQL database of your choice. And while doing so, I noticed that caching plays a major role if you want to extend ActionView::Resolver for your own projects. That is because the most basic method for Resolver is find_all. From the Rails API:

find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])

Normalizes the arguments and passes it on to find_template.

And the source code:

If I am not wrong, the yield for cached is provided somewhere else up in the inheritance, but that does not really matter to us right now. What really trickled me out is the nested hash that it uses. I knew there should be some reason to use this syntax so I decided to research further.

Why to use a nested hash to cache templates instead of using any other key?

We could use nested hashes, a simple hash with an array as the key, or a simple hash with the hash as the key. In short, Ruby hashes create a sort of table that stores the hash, key and value. In fact we can think of adding new elements to an existing hash as adding new records to a table. Well, and just a remainder of Data Structures 101, whenever you call hashset[:key], what Ruby does is to calculate the hash for that key and return all the values that match that hash within the hashset. This is done like this for a pure performance reason, it makes a lot of sense to compare just integers (the hashes) instead of the actual objects (the values) which can potentially be a lot more sluggish.

When Object#hash creates a hash given a nested hash as key (i.e: hash[a][b][c][d]), just like ActiveView::Resolver does, the hash keys are either strings or booleans, and luckily that is blatant fast. If we try to get a hash given an array or hash as the key, that’s a lot slower.

I found this benchmark on this online again by awesome Jose Valim, so just take a look at it, and if you feel more intrigued about this, check my repository SQLResolver to see how ActionView::Resolver can be finely honed for your own needs.