Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

« Newer Snippets
Older Snippets »
Showing 1-4 of 4 total  RSS 

Automatic Expiration of Rails Action Caching

Would love to get some feedback on this...

No doubt, 5 minutes after posting this, someone will tell me of the built-in Rails way of doing this, but alas I could not find it.

Usage looks like this:

   1  
   2  class PeopleController < ApplicationController
   3    caches_action :show, :for => 1.hour, :cache_path => Proc.new { |c| "people/#{c.params[:id]}_for_#{Person.logged_in.id}" }
   4  end


The ":for => 1.hour" part is where the magic happens.

Basically, this bit of code adds a before_filter that checks the last modified time of the cache entry, and expires it if it is older than the specified time period.

   1  
   2  module ActionController
   3    module Caching
   4      module Fragments
   5        # expire a cache key only if the block returns true or
   6        # if the age of the fragment is more than the specified age argument.
   7        def expire_fragment_by_mtime(key, age=nil, &block)
   8          block = Proc.new { |m| m < age.ago } unless block_given?
   9          if (m = cache_store.mtime(fragment_cache_key(key))) and block.call(m)
  10            expire_fragment(key)
  11          end
  12        end
  13      end
  14      module Actions
  15        module ClassMethods
  16          # adds an option :for
  17          # caches_action :show, :for => 2.hours, :cache_path => ...
  18          # :cache_path is required, unfortunately
  19          def caches_action_with_for(*actions)
  20            original_actions = actions.clone
  21            options = actions.extract_options!
  22            if for_time = options.delete(:for)
  23              cache_path = options[:cache_path]
  24              before_filter do |controller|
  25                cache_path = cache_path.call(controller) if cache_path.respond_to?(:call)
  26                controller.expire_fragment_by_mtime(cache_path, for_time)
  27              end
  28            end
  29            caches_action_without_for(*original_actions)
  30          end
  31          alias_method_chain :caches_action, :for
  32        end
  33      end
  34    end
  35  end
  36  
  37  # Add a method to grab the last modified time of the cache key.
  38  # If you use a store other than the FileStore, you'll need to add
  39  # a method like this to your store.
  40  module ActiveSupport
  41    module Cache
  42      class FileStore
  43        def mtime(name)
  44          File.mtime(real_file_path(name)) rescue nil
  45        end
  46      end
  47    end
  48  end

Object.memoize

The following is inspired by the article "Caching and Memoization" by James Edward Gray II.

   1  
   2  # = Memoization for objects
   3  # 
   4  # This module will extend +Object+ with the +memoize+ method.  This method
   5  # provides memoization for instance methods, which means return values will
   6  # be cached and subsequent calls will return the cached value of the first
   7  # call.
   8  # 
   9  # Caching is done based on instance, method and arguments to the method.  All
  10  # data is kept in a single +Hash+ store which allows flushing all cached
  11  # results at ones using the +flush_memos+ method.
  12  # 
  13  # == Example
  14  #   class Person < Struct.new(:email)
  15  #     def finger
  16  #       `finger #{email}`
  17  #     end
  18  #     memoize :finger
  19  #   end
  20  #   
  21  #   bob = Person.new('bob@test.net')
  22  #   bob.finger                        # finger command executed
  23  #   bob.finger                        # cached value returned
  24  #   Memoizable.flush_memos
  25  #   bob.finger                        # finger command executed
  26  #
  27  # == See also
  28  # http://blog.grayproductions.net/articles/caching_and_memoization
  29  #
  30  # == Author
  31  # R.W. van 't Veer, 2008-04-01, Amsterdam
  32  module Memoizable
  33    # Store for cached values.
  34    CACHE = Hash.new{|h,k| h[k] = Hash.new{|h,k| h[k] = {}}} # 3 level hash; CACHE[:foo][:bar][:yelp]
  35    
  36    # Memoize the given method(s).
  37    def memoize(*names)
  38      names.each do |name|
  39        unmemoized = "__unmemoized_#{name}"
  40        
  41        class_eval %Q{
  42          alias   :#{unmemoized} :#{name}
  43          private :#{unmemoized}
  44          def #{name}(*args)
  45            cache = CACHE[self][#{name.inspect}]
  46            cache.has_key?(args) ? cache[args] : (cache[args] = send(:#{unmemoized}, *args))
  47          end
  48        }
  49      end
  50    end
  51    
  52    # Flush cached return values.
  53    def flush_memos
  54      CACHE.clear
  55    end
  56    module_function :flush_memos
  57  end
  58  
  59  class Object # :nodoc:
  60    extend Memoizable
  61  end
  62  
  63  if $0 == __FILE__
  64    require 'test/unit'
  65    
  66    class MemoizeTest < Test::Unit::TestCase # :nodoc:
  67      def setup
  68        @obj = TestObject.new
  69      end
  70      
  71      def teardown
  72        Memoizable.flush_memos
  73      end
  74      
  75      def test_memoize_value_should_stick_until_cache_flushed
  76        @obj.value = 'a'
  77        assert_equal 'a', @obj.value
  78        
  79        @obj.value = 'b'
  80        assert_equal 'a', @obj.value
  81        
  82        Memoizable.flush_memos
  83        assert_equal 'b', @obj.value
  84      end
  85      
  86      def test_flush_should_clear_all_cached_objects
  87        @obj.value = 'yelp'
  88        @obj.value
  89        
  90        assert_not_equal 0, Memoizable::CACHE.size
  91        Memoizable.flush_memos
  92        assert_equal 0, Memoizable::CACHE.size
  93      end
  94      
  95      def test_memoize_should_keep_separate_cache_per_instance
  96        other = TestObject.new
  97        @obj.value, other.value = 'a', 'b'
  98        
  99        assert_equal 'a', @obj.value
 100        assert_equal 'b', other.value
 101      end
 102      
 103      def test_memoize_should_keep_separate_cache_per_method
 104        @obj.value, @obj.other = 'a', 'b'
 105        
 106        assert_equal 'a', @obj.value
 107        assert_equal 'b', @obj.other
 108      end
 109      
 110      def test_memoize_should_include_arguments_in_cache_key
 111        @obj.with_arguments = 'a'
 112        assert_equal 'a', @obj.with_arguments(:this)
 113        
 114        @obj.with_arguments = 'b'
 115        assert_equal 'a', @obj.with_arguments(:this)
 116        assert_equal 'b', @obj.with_arguments(:that)
 117      end
 118      
 119      class TestObject # :nodoc:
 120        attr_accessor :value, :other
 121        memoize :value, :other
 122        
 123        attr_writer :with_arguments
 124        def with_arguments(*args); @with_arguments; end
 125        memoize :with_arguments
 126        
 127        attr_writer :question
 128        def question?; @question; end
 129        memoize :question?
 130        
 131        attr_writer :exclamation
 132        def exclamation!; @exclamation; end
 133        memoize :exclamation!
 134      end
 135    end
 136  end

Simple cacher module for Rails

Illustration of a simple cacher module for Rails.

   1  
   2  # lib/cacher.rb
   3  module Cacher
   4    STORE = {}
   5    
   6    def cache(*args)
   7      STORE[args.inspect] ||= yield
   8    end
   9  
  10    def flush!
  11      STORE.clear
  12    end
  13    module_function :flush!
  14  end
  15  
  16  # app/models/person.rb
  17  class Person < AR::Base
  18    include Cacher
  19  
  20    def finger
  21      cache(:finger, email) do
  22        `finger #{email}`
  23      end
  24    end
  25  end
  26  
  27  # app/controller/application.rb
  28  class ApplicationController < AC::Base
  29    after_filter { Cacher.flush! }
  30  end

response caching in camping

A basic implementation of response caching in camping.

   1  
   2  Camping.goes :MyCampingApp
   3  
   4  module MyCampingApp
   5    module Controller
   6      class View < R '/'
   7        def get
   8          cache('root') do
   9            'Expensive operation!'
  10          end
  11        end
  12      end
  13    end
  14    
  15    def flush(id)
  16      f = File.dirname(__FILE__) + "/cache/#{id}"
  17      File.delete(f) if File.exists?(f)
  18    end
  19    
  20    def cache(id, timeout = 1.hour)
  21      f = File.dirname(__FILE__) + "/cache/#{id}"
  22      
  23      if File.exists?(f) && (Time.now - File.stat(f).mtime) < timeout
  24        File.read(f)
  25      else
  26        r = yield
  27        open(f, 'w'){|wr| wr.write(r)}
  28        r
  29      end
  30    end
  31  
  32    def self.create
  33      cache_dir = File.dirname(__FILE__) + "/cache"
  34      Dir.mkdir(cache_dir) unless File.directory?(cache_dir)
  35    end
  36  end
« Newer Snippets
Older Snippets »
Showing 1-4 of 4 total  RSS