Expiring Rails File Cache by Time

» 13 Jul 2009

One of the major weaknesses of the Rails cache file store is that cached pages cannot be expired by time. This poses a problem for keeping caching as simple as possible.

The solution I came up with stores cached content as a JSON file containing the content and the ttl.

Expiration needs only be set when cache is called. read_fragment should know nothing about expiration.

<% cache('home_most_popular', :expires_in => 1.day) do %>
    <%= render :partial => 'most_popular' %>
  <% end %>

The code that makes this work should go in lib/hash_file_store.rb

require 'json'
  
  class HashFileStore < ActiveSupport::Cache::FileStore                                
    def write(name, value, options = nil)
      ttl = 0   
      if options.is_a?(Hash) && options.has_key?(:expires_in)                          
        ttl = options[:expires_in]                                                     
        #options.delete(:expires_in)                                                   
      end
      value = JSON.generate({'ttl' => ttl, 'value' => value.dup})                      
      super
    end 
  
    def read(name, options = nil)                                                      
      value = super
      return unless value                                                              

      #malformed JSON is the same as no JSON
      value = JSON.parse(value) rescue nil 

      fn = real_file_path(name)                                                        
      if value && (value['ttl'] == 0 || 
        (File.mtime(fn) > (Time.now - value['ttl'])))  
        
        value['value']                                                                 
      else
        nil
      end
    end
  end

Put the following line in config/environment.rb and it should be good to go.

ActionController::Base.cache_store = :hash_file_store, 
    "#{RAILS_ROOT}/tmp/cache"