local_cache.rb 4.6 KB
Newer Older
1
require 'active_support/core_ext/object/duplicable'
X
Xavier Noria 已提交
2
require 'active_support/core_ext/string/inflections'
3
require 'active_support/per_thread_registry'
4

5 6 7
module ActiveSupport
  module Cache
    module Strategy
8
      # Caches that implement LocalCache will be backed by an in-memory cache for the
B
Brian Durand 已提交
9
      # duration of a block. Repeated calls to the cache for the same key will hit the
10
      # in-memory cache for faster access.
11
      module LocalCache
A
Arthur Neves 已提交
12 13
        autoload :Middleware, 'active_support/cache/strategy/local_cache_middleware'

14
        # Class for storing and registering the local caches.
15
        class LocalCacheRegistry # :nodoc:
16 17 18 19 20 21 22 23 24 25 26 27 28
          extend ActiveSupport::PerThreadRegistry

          def initialize
            @registry = {}
          end

          def cache_for(local_cache_key)
            @registry[local_cache_key]
          end

          def set_cache_for(local_cache_key, value)
            @registry[local_cache_key] = value
          end
29 30 31

          def self.set_cache_for(l, v); instance.set_cache_for l, v; end
          def self.cache_for(l); instance.cache_for l; end
32 33
        end

34
        # Simple memory backed cache. This cache is not thread safe and is intended only
B
Brian Durand 已提交
35 36 37 38 39 40 41
        # for serving as a temporary memory cache for a single thread.
        class LocalStore < Store
          def initialize
            super
            @data = {}
          end

S
Sushruth Sivaramakrishnan 已提交
42
          # Don't allow synchronizing since it isn't thread safe.
B
Brian Durand 已提交
43 44 45 46 47 48 49 50 51 52 53
          def synchronize # :nodoc:
            yield
          end

          def clear(options = nil)
            @data.clear
          end

          def read_entry(key, options)
            @data[key]
          end
54

B
Brian Durand 已提交
55 56 57 58 59 60 61 62
          def write_entry(key, value, options)
            @data[key] = value
            true
          end

          def delete_entry(key, options)
            !!@data.delete(key)
          end
63 64 65 66

          def fetch_entry(key, options = nil) # :nodoc:
            @data.fetch(key) { @data[key] = yield }
          end
B
Brian Durand 已提交
67 68
        end

69
        # Use a local cache for the duration of block.
70
        def with_local_cache
71
          use_temporary_local_cache(LocalStore.new) { yield }
72
        end
73 74 75 76 77
        # Middleware class can be inserted as a Rack handler to be local cache for the
        # duration of request.
        def middleware
          @middleware ||= Middleware.new(
            "ActiveSupport::Cache::Strategy::LocalCache",
78
            local_cache_key)
79 80
        end

B
Brian Durand 已提交
81 82
        def clear(options = nil) # :nodoc:
          local_cache.clear(options) if local_cache
83 84 85
          super
        end

B
Brian Durand 已提交
86 87
        def cleanup(options = nil) # :nodoc:
          local_cache.clear(options) if local_cache
88 89 90
          super
        end

B
Brian Durand 已提交
91 92
        def increment(name, amount = 1, options = nil) # :nodoc:
          value = bypass_local_cache{super}
93
          set_cache_value(value, name, amount, options)
B
Brian Durand 已提交
94
          value
95 96
        end

B
Brian Durand 已提交
97 98
        def decrement(name, amount = 1, options = nil) # :nodoc:
          value = bypass_local_cache{super}
99
          set_cache_value(value, name, amount, options)
B
Brian Durand 已提交
100
          value
101 102
        end

B
Brian Durand 已提交
103 104
        protected
          def read_entry(key, options) # :nodoc:
105
            if cache = local_cache
106
              cache.fetch_entry(key) { super }
B
Brian Durand 已提交
107 108 109
            else
              super
            end
110 111
          end

B
Brian Durand 已提交
112 113 114 115 116 117 118 119 120
          def write_entry(key, entry, options) # :nodoc:
            local_cache.write_entry(key, entry, options) if local_cache
            super
          end

          def delete_entry(key, options) # :nodoc:
            local_cache.delete_entry(key, options) if local_cache
            super
          end
121

122
          def set_cache_value(value, name, amount, options) # :nodoc:
123 124 125 126 127 128 129 130 131 132 133
            if local_cache
              local_cache.mute do
                if value
                  local_cache.write(name, value, options)
                else
                  local_cache.delete(name, options)
                end
              end
            end
          end

134 135
        private

136 137
          def local_cache_key
            @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym
138 139 140
          end

          def local_cache
141
            LocalCacheRegistry.cache_for(local_cache_key)
142
          end
B
Brian Durand 已提交
143 144

          def bypass_local_cache
145 146 147 148
            use_temporary_local_cache(nil) { yield }
          end

          def use_temporary_local_cache(temporary_cache)
149
            save_cache = LocalCacheRegistry.cache_for(local_cache_key)
B
Brian Durand 已提交
150
            begin
151
              LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache)
B
Brian Durand 已提交
152 153
              yield
            ensure
154
              LocalCacheRegistry.set_cache_for(local_cache_key, save_cache)
B
Brian Durand 已提交
155 156
            end
          end
157 158 159 160
      end
    end
  end
end