One of my current projects at work involves ajax rating for videos. The problem is the index page displays 24 videos. That’s 24 additional queries per page load, or an excessive amount of joins. To speed this up, I implemented fragment caching. The problem I ran into was that even when console stated that it expired the cache when I would rate a video, the cache file would never be removed, and thus, on page reload, would display the original rating. I thought I was going crazy so I dug into the Rails code, particularly ActiveSupport::Cache::FileStore and took a peek at delete. What I found was amazingly silly.
1 2 3 4 5 6 | def delete(name, options = nil) super File.delete(real_file_path(name)) rescue SystemCallError => e # If there's no cache, then there's nothing to complain about end |
Look close at the rescue block. “If there’s no cache, then there’s nothing to complain about”. What if the cache exists, but there is another possible error? How would I know what the error was? Since I know the cache existed in tmp/cache/ I decided to monkey patch it.
1 2 3 4 5 6 7 8 9 | ActiveSupport::Cache::FileStore.class_eval do def delete(name, options = nil) puts "Real Filename: #{real_file_path(name)}" super File.delete(real_file_path(name)) rescue SystemCallError => e puts "Failed to delete cache. #{e.message}" end end |
What I found was fragment caching was reading and writing cache to one path, then trying to delete from another. It turns out I was not paying attention and expire_fragment treats a hash as parameters for url_for. Changing my key to a string solved it.
So what have we learned? Just because Rails rocks doesn’t mean some code isn’t ass backwards. Never be afraid to jump in and debug core for yourself.
Related posts: