Warning: call_user_func() expects parameter 1 to be a valid callback, no array or string given in /home/robzbd/sites/blog.ubrio.us/wp-content/plugins/akismet/widget.php on line 126

Warning: ob_start(): non-static method wpGoogleAnalytics::get_links() should not be called statically in /home/robzbd/sites/blog.ubrio.us/wp-content/plugins/wp-google-analytics/wp-google-analytics.php on line 288
Simple rails time based fragment caching with file store

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:

  1. You can use it in the controller: fragment_exist?(...)
  2. You can use it in views: cache(...) {}
  3. Very lightweight
  4. Behaves like the regular file store caching
  5. 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.



Comments

  1. Tim Knauf May 5th

    Comment Arrow

    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.


  2. Parker Morse May 5th

    Comment Arrow

    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

  • Author Avatar

    YOU


Comment Arrow




About Author

Rob Hurring

Ruby, Rails, PHP, bash... oh my!