What's New in Edge Rails: Pluggable Controller Caching

Posted by ryan
at 7:45 AM on Wednesday, December 19, 2007

It appears that Rails controller will no longer be limited to file-based page and fragment caching. There is a lot of work being done in the 2-1-caching branch of Rails that will let you specify your preferred caching engine in the config file. To date there are the following options:

1
2
3
4
ActionController::Base.cache_store = :memory_store
ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
ActionController::Base.cache_store = :mem_cache_store, "localhost"

If none is specified, then ActiveSupport::Cache::MemoryStore will be used.

Write Your Own Controller Cache

Interested in writing your own controller caching mechanism? Here are some basic steps:

1. Implement the Cache Class

The easiest way to write your own cache class is to subclass ActiveSupport::Cache::Store and implement the read, write, delete and delete_matched methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
module ActiveSupport
  module Cache

    # Pluck and store stuff in the ether.
    # ('Ether' is fictitious - ignore its implementation)
    class EtherStore < Store

      def initialize(location='//myloc')
        @ether = Ether.new(location)
      end

      def read(name, options = nil)
        super
        @ether.get(name)
      end

      def write(name, value, options = nil)
        super
        @ether.store(name, value)
      end

      def delete(name, options = nil)
        super
        @ether.remove(name)
      end
        
      def delete_matched(matcher, options = nil)
        super
        @ether.remove_by_pattern(matcher)
      end
    end
  end
end
A few notes:
  • Invoking super for each method call ensures that the proper messages get logged for each action
  • If the underlying cache mechanism doesn’t support a specific operation (such as delete_matched), have your implementation just raise an exception: raise "delete_matched not supported by Mechanism"
  • The options argument passed to each method is taken directly from the various top-level cache methods which now accept an options hash (you now have a method to pass in options applicable to your caching mechanism). I.e.in your view when fragment caching:
1
2
3
<% cache('name', :expires_in => 60) do %>
  <%= render :partial => "menu" %>
<% end %>
2. Plug-in Cache

Once you have your implementation (and it’s tested, of course), plug it into your app with a simple one-liner in environment.rb:

1
2
# Or EtherStore.new('//etherloc')
ActionController::Base.cache_store = :ether_store, '//etherloc'

There you go, that’s all there is to it.

I’m not sure what the intent is for this functionality, but as of now it only exists in the 2-1-caching branch of rails, I will update this post when it makes it to edge.

tags: ruby, rubyonrails

Comments

Leave a response

  1. Dee ZsomborDecember 20, 2007 @ 04:45 AM

    I’m pretty sure that this does not increase the number of caching options available. You can write a custom fragment store already, and you can use other than the file based simplistic route. I’m right now using a memcache for storage and the setup is as simple like:

    <table class="CodeRay"><tr> <td title="click to toggle" class="line_numbers" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }">
    1
    2
    3
    4
    5
    6
    7
    
    </td> <td class="code">
    CACHE = MemCache.new(:c_threshold => 10_000,
                         :compression => true,
                         :debug => false,
                         :readonly => false,
                         :urlencode => false)
    CACHE.servers = ['10.0.0.3 :11211', '10.0.0.4 :11211']
    config.action_controller.fragment_cache_store = CACHE, {}
    </td> </tr></table>

    Note how all the magic is really in the last line, the other is just memcache config.

    The 2-1-caching branch seems more about refactoring than a features, perhaps the gzip compression could be called a feature but even there i feel it is more about elegance.

  2. Dee ZsomborDecember 20, 2007 @ 04:47 AM

    Hit by the Mephisto code in comment bug ;-)

  3. Benjamin CurtisDecember 20, 2007 @ 07:40 AM

    And you can use plugins like memcache_fragments (http://agilewebdevelopment.com/plugins/memcache_fragments_with_time_expiry) to get Rails to play nicely with memcache-client, making it easy to do supa-fast action caching with memcache without having to worry about expiring stale caches:

    http://www.bencurtis.com/archives/2007/12/painless-rails-action-caching-with-memcached/#more-111

  4. rnbFebruary 25, 2008 @ 11:49 AM

    Thanks for the nice read, keep up the interesting posts.