public
Fork of defunkt/cache_fu
Description: Everyone's favorite memcached plugin for ActiveRecord.
Homepage: http://errtheblog.com
Clone URL: git://github.com/technoweenie/cache_fu.git
cache_fu / lib / acts_as_cached / fragment_cache.rb
100644 119 lines (105 sloc) 4.894 kb
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
module ActsAsCached
  module FragmentCache
    def self.setup!
      class << CACHE
        include Extensions
      end
      
      setup_fragment_cache_cache
      setup_rails_for_memcache_fragments
      setup_rails_for_action_cache_options
    end
    
    # add :ttl option to cache helper and set cache store memcache object
    def self.setup_rails_for_memcache_fragments
      ::ActionView::Helpers::CacheHelper.class_eval do
        def cache(name = {}, options = nil, &block)
          @controller.cache_erb_fragment(block, name, options)
        end
      end unless ::ActionView.const_defined?(:Template)
      ::ActionController::Base.fragment_cache_store = CACHE
    end
 
    def self.setup_fragment_cache_cache
      Object.const_set(:FragmentCacheCache, Class.new { acts_as_cached :store => CACHE })
    end
    
    # add :ttl option to caches_action on the per action level by passing in a hash instead of an array
    #
    # Examples:
    # caches_action :index # will use the default ttl from your memcache.yml, or 25 minutes
    # caches_action :index => { :ttl => 5.minutes } # cache index action with 5 minute ttl
    # caches_action :page, :feed, :index => { :ttl => 2.hours } # cache index action with 2 hours ttl, all others use default
    #
    def self.setup_rails_for_action_cache_options
      ::ActionController::Caching::Actions::ActionCacheFilter.class_eval do
        # convert all actions into a hash keyed by action named, with a value of a ttl hash (to match other cache APIs)
        def initialize(*actions, &block)
          if [].respond_to?(:extract_options!)
            #edge
            @options = actions.extract_options!
            @actions = actions.inject(@options.except(:cache_path)) do |hsh, action|
              action.is_a?(Hash) ? hsh.merge(action) : hsh.merge(action => { :ttl => nil })
            end
            @options.slice!(:cache_path)
          else
            #1.2.5
            @actions = actions.inject({}) do |hsh, action|
              action.is_a?(Hash) ? hsh.merge(action) : hsh.merge(action => { :ttl => nil })
            end
          end
        end
 
        # override to skip caching/rendering on evaluated if option
        def before(controller)
          return unless @actions.include?(controller.action_name.intern)
 
          # maintaining edge and 1.2.x compatibility with this branch
          if @options
            action_cache_path = ActionController::Caching::Actions::ActionCachePath.new(controller, path_options_for(controller, @options))
          else
            action_cache_path = ActionController::Caching::Actions::ActionCachePath.new(controller)
          end
          
          # should probably be like ActiveRecord::Validations.evaluate_condition. color me lazy.
          if conditional = @actions[controller.action_name.intern][:if]
            conditional = conditional.respond_to?(:call) ? conditional.call(controller) : controller.send(conditional)
          end
          @actions.delete(controller.action_name.intern) if conditional == false
 
          cache = controller.read_fragment(action_cache_path.path)
          if cache && (conditional || conditional.nil?)
            controller.rendered_action_cache = true
            if method(:set_content_type!).arity == 2
              set_content_type!(controller, action_cache_path.extension)
            else
              set_content_type!(action_cache_path)
            end
            controller.send(:render, :text => cache)
            false
          else
            # 1.2.x compatibility
            controller.action_cache_path = action_cache_path if controller.respond_to? :action_cache_path
          end
        end
        
        # override to pass along the ttl hash
        def after(controller)
          return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
          # 1.2.x compatibility
          path = controller.respond_to?(:action_cache_path) ? controller.action_cache_path.path : ActionController::Caching::Actions::ActionCachePath.path_for(controller)
          controller.write_fragment(path, controller.response.body, action_ttl(controller))
        end
 
        private
        def action_ttl(controller)
          @actions[controller.action_name.intern]
        end
      end
    end
 
    module Extensions
      def read(*args)
        return if ActsAsCached.config[:skip_gets]
        FragmentCacheCache.cache_store(:get, args.first)
      end
      
      def write(name, content, options = {})
        ttl = (options.is_a?(Hash) ? options[:ttl] : nil) || ActsAsCached.config[:ttl] || 25.minutes
        FragmentCacheCache.cache_store(:set, name, content, ttl)
      end
    end
 
    module DisabledExtensions
      def read(*args) nil end
      def write(*args) "" end
    end
  end
end