Simple rails time based fragment caching with file store
I spent a little while this morning looking around for a rails fragment caching solution which used a file store and had simple expirations built in. No luck. I could just use a memory based cache store, but for this particular problem it was too much overhead to setup and manage.
The features are:
- You can use it in the controller:
fragment_exist?(...) - You can use it in views:
cache(...) {} - Very lightweight
- Behaves like the regular file store caching
- Can expire caches automatically
Timed file store code
# lib/timed_file_store.rb
class TimedFileStore < ActiveSupport::Cache::FileStore
def exist?(name, options = {})
delete_if_expired(name, options[:time_to_live]) unless options.blank? or options[:time_to_live].blank?
super
end
def read(name, options = {})
delete_if_expired(name, options[:time_to_live]) unless options.blank? or options[:time_to_live].blank?
super
end
protected
def delete_if_expired(name, time_to_live = 0)
delete(name) if expired?(name, time_to_live) rescue nil
end
def expired?(name, time_to_live = 0)
return false unless time_to_live > 0
(Time.now - File.mtime(real_file_path(name))) >= time_to_live
end
end
Setup & Usage
# environment.rb # same as :file_store -- just add timed_ config.cache_store = :timed_file_store, File.join(RAILS_ROOT, 'tmp', 'cache')
Time based expiration from the controller
Having the controller control the expiration of the cache is good if you are passing along any objects to the views since it will not re-run the code if it is already cached. I’m using this for a long running report currently.
# YourController.rb
# don't run the report if we are cached
@report = Report.find(params[:id])
unless fragment_exists?("report_#{@report.id}", :time_to_live => 1.week)
@report.run!
end
# views/reports/show.html.erb -- or whatever view file
<% cache("report_#{@report.id}") do -%>
<!-- output the report HTML and such -->
<% end -%>
This will expire the fragment and re-run the report after the cache’s File.mtime is older than a week. The view doesn’t have anything to do with it so it acts like the normal FileStore.
Time based expiration in the view
If you aren’t running any intense code in the controller and just want to keep a certain view fragment cached for a while, this should work. (** I didn’t test this since I only needed the previous method, but it should work — maybe with some slight tweaks?
# views/wherever/whatever.html.erb
<% cache("whatever", :time_to_live => 1.hour) do -%>
<!-- This is my HTML content and all that jazz -->
<!-- After 1 hour this cache should be re-freshed -->
<% end -%>
Not sure how useful that would be when considering action and page caching… but its possible. The only thing I noticed is that in the log when the cache deletes itself it shows a “Cached fragment hit:” then a “Cached fragment miss:” — probably needs some attention if I ever get around to it.
Let me know if this works out, and/or any issues, bugs, etc.







Tim Knauf May 5th
Thank you for this! I wanted to ensure that our site’s twitter feed would only query the twitter API a few times per hour, and this technique is ideal.
Parker Morse May 5th
We’ve made this work in views; the syntax is different for fragment caching, however. To work with a fragment, it needs to be cache({:fragment => “whatever”}, :time_to_live => 1.hour) do . (If it’s a site-wide fragment, your syntax works fine.)
Add Yours
YOU