cache.rb 9.3 KB
Newer Older
1
require 'benchmark'
J
Jeremy Kemper 已提交
2
require 'active_support/core_ext/array/wrap'
3 4 5
require 'active_support/core_ext/benchmark'
require 'active_support/core_ext/exception'
require 'active_support/core_ext/class/attribute_accessors'
6
require 'active_support/core_ext/object/to_param'
7
require 'active_support/core_ext/string/inflections'
8

9
module ActiveSupport
P
Pratik Naik 已提交
10
  # See ActiveSupport::Cache::Store for documentation.
11
  module Cache
J
Jeremy Kemper 已提交
12 13 14 15 16 17
    autoload :FileStore, 'active_support/cache/file_store'
    autoload :MemoryStore, 'active_support/cache/memory_store'
    autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store'
    autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
    autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'

18 19 20 21
    module Strategy
      autoload :LocalCache, 'active_support/cache/strategy/local_cache'
    end

P
Pratik Naik 已提交
22 23 24 25 26 27 28 29 30 31 32
    # Creates a new CacheStore object according to the given options.
    #
    # If no arguments are passed to this method, then a new
    # ActiveSupport::Cache::MemoryStore object will be returned.
    #
    # If you pass a Symbol as the first argument, then a corresponding cache
    # store class under the ActiveSupport::Cache namespace will be created.
    # For example:
    #
    #   ActiveSupport::Cache.lookup_store(:memory_store)
    #   # => returns a new ActiveSupport::Cache::MemoryStore object
33
    #
34 35
    #   ActiveSupport::Cache.lookup_store(:mem_cache_store)
    #   # => returns a new ActiveSupport::Cache::MemCacheStore object
P
Pratik Naik 已提交
36 37 38 39 40 41 42 43 44 45 46
    #
    # Any additional arguments will be passed to the corresponding cache store
    # class's constructor:
    #
    #   ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache")
    #   # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache")
    #
    # If the first argument is not a Symbol, then it will simply be returned:
    #
    #   ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
    #   # => returns MyOwnCacheStore.new
47
    def self.lookup_store(*store_option)
J
Jeremy Kemper 已提交
48
      store, *parameters = *Array.wrap(store_option).flatten
49 50 51

      case store
      when Symbol
52
        store_class_name = store.to_s.camelize
53 54 55 56 57 58 59 60 61
        store_class = ActiveSupport::Cache.const_get(store_class_name)
        store_class.new(*parameters)
      when nil
        ActiveSupport::Cache::MemoryStore.new
      else
        store
      end
    end

Y
Yehuda Katz 已提交
62 63 64 65
    RAILS_CACHE_ID   = ENV["RAILS_CACHE_ID"]
    RAILS_APP_VERION = ENV["RAILS_APP_VERION"]
    EXPANDED_CACHE   = RAILS_CACHE_ID || RAILS_APP_VERION

66 67
    def self.expand_cache_key(key, namespace = nil)
      expanded_cache_key = namespace ? "#{namespace}/" : ""
68

Y
Yehuda Katz 已提交
69 70
      if EXPANDED_CACHE
        expanded_cache_key << "#{RAILS_CACHE_ID || RAILS_APP_VERION}/"
71 72
      end

Y
Yehuda Katz 已提交
73 74
      expanded_cache_key <<
        if key.respond_to?(:cache_key)
75
          key.cache_key
Y
Yehuda Katz 已提交
76 77 78 79 80 81 82
        elsif key.is_a?(Array)
          if key.size > 1
            key.collect { |element| expand_cache_key(element) }.to_param
          else
            key.first.to_param
          end
        elsif key
83 84
          key.to_param
        end.to_s
85 86 87 88

      expanded_cache_key
    end

P
Pratik Naik 已提交
89 90 91 92 93 94 95 96 97 98 99
    # An abstract cache store class. There are multiple cache store
    # implementations, each having its own additional features. See the classes
    # under the ActiveSupport::Cache module, e.g.
    # ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most
    # popular cache store for large production websites.
    #
    # ActiveSupport::Cache::Store is meant for caching strings. Some cache
    # store implementations, like MemoryStore, are able to cache arbitrary
    # Ruby objects, but don't count on every cache store to be able to do that.
    #
    #   cache = ActiveSupport::Cache::MemoryStore.new
100
    #
P
Pratik Naik 已提交
101 102 103
    #   cache.read("city")   # => nil
    #   cache.write("city", "Duckburgh")
    #   cache.read("city")   # => "Duckburgh"
104
    class Store
105
      cattr_accessor :logger, :instance_writter => false
106

J
José Valim 已提交
107 108
      attr_reader :silence
      alias :silence? :silence
109

110 111 112 113 114
      def silence!
        @silence = true
        self
      end

115 116 117 118 119 120 121
      def mute
        previous_silence, @silence = defined?(@silence) && @silence, true
        yield
      ensure
        @silence = previous_silence
      end

122 123 124 125 126 127 128 129 130
      # Set to true if cache stores should be instrumented. By default is false.
      def self.instrument=(boolean)
        Thread.current[:instrument_cache_store] = boolean
      end

      def self.instrument
        Thread.current[:instrument_cache_store] || false
      end

P
Pratik Naik 已提交
131 132 133 134 135 136 137 138 139 140 141
      # Fetches data from the cache, using the given key. If there is data in
      # the cache with the given key, then that data is returned.
      #
      # If there is no such data in the cache (a cache miss occurred), then
      # then nil will be returned. However, if a block has been passed, then
      # that block will be run in the event of a cache miss. The return value
      # of the block will be written to the cache under the given cache key,
      # and that return value will be returned.
      #
      #   cache.write("today", "Monday")
      #   cache.fetch("today")  # => "Monday"
142
      #
P
Pratik Naik 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
      #   cache.fetch("city")   # => nil
      #   cache.fetch("city") do
      #     "Duckburgh"
      #   end
      #   cache.fetch("city")   # => "Duckburgh"
      #
      # You may also specify additional options via the +options+ argument.
      # Setting <tt>:force => true</tt> will force a cache miss:
      #
      #   cache.write("today", "Monday")
      #   cache.fetch("today", :force => true)  # => nil
      #
      # Other options will be handled by the specific cache store implementation.
      # Internally, #fetch calls #read, and calls #write on a cache miss.
      # +options+ will be passed to the #read and #write calls.
      #
      # For example, MemCacheStore's #write method supports the +:expires_in+
      # option, which tells the memcached server to automatically expire the
161 162
      # cache item after a certain period. This options is also supported by
      # FileStore's #read method. We can use this option with #fetch too:
P
Pratik Naik 已提交
163 164 165 166 167 168 169 170
      #
      #   cache = ActiveSupport::Cache::MemCacheStore.new
      #   cache.fetch("foo", :force => true, :expires_in => 5.seconds) do
      #     "bar"
      #   end
      #   cache.fetch("foo")  # => "bar"
      #   sleep(6)
      #   cache.fetch("foo")  # => nil
J
José Valim 已提交
171
      def fetch(key, options = {}, &block)
172
        if !options[:force] && value = read(key, options)
173 174
          value
        elsif block_given?
J
José Valim 已提交
175 176 177
          result = instrument(:generate, key, options, &block)
          write(key, result, options)
          result
178 179 180
        end
      end

P
Pratik Naik 已提交
181 182 183 184 185 186 187
      # Fetches data from the cache, using the given key. If there is data in
      # the cache with the given key, then that data is returned. Otherwise,
      # nil is returned.
      #
      # You may also specify additional options via the +options+ argument.
      # The specific cache store implementation will decide what to do with
      # +options+.
188 189 190 191
      #
      # For example, FileStore supports the +:expires_in+ option, which
      # makes the method return nil for cache items older than the specified
      # period.
J
José Valim 已提交
192 193
      def read(key, options = nil, &block)
        instrument(:read, key, options, &block)
194 195
      end

P
Pratik Naik 已提交
196 197 198 199 200
      # Writes the given value to the cache, with the given key.
      #
      # You may also specify additional options via the +options+ argument.
      # The specific cache store implementation will decide what to do with
      # +options+.
201
      #
P
Pratik Naik 已提交
202 203 204 205 206 207 208 209 210
      # For example, MemCacheStore supports the +:expires_in+ option, which
      # tells the memcached server to automatically expire the cache item after
      # a certain period:
      #
      #   cache = ActiveSupport::Cache::MemCacheStore.new
      #   cache.write("foo", "bar", :expires_in => 5.seconds)
      #   cache.read("foo")  # => "bar"
      #   sleep(6)
      #   cache.read("foo")  # => nil
J
José Valim 已提交
211 212
      def write(key, value, options = nil, &block)
        instrument(:write, key, options, &block)
213 214
      end

J
José Valim 已提交
215 216
      def delete(key, options = nil, &block)
        instrument(:delete, key, options, &block)
217 218
      end

J
José Valim 已提交
219 220
      def delete_matched(matcher, options = nil, &block)
        instrument(:delete_matched, matcher.inspect, options, &block)
221 222
      end

J
José Valim 已提交
223 224
      def exist?(key, options = nil, &block)
        instrument(:exist?, key, options, &block)
225 226
      end

227 228 229 230 231 232
      def increment(key, amount = 1)
        if num = read(key)
          write(key, num + amount)
        else
          nil
        end
233 234
      end

235 236 237 238 239 240 241
      def decrement(key, amount = 1)
        if num = read(key)
          write(key, num - amount)
        else
          nil
        end
      end
242

243
      private
244
        def expires_in(options)
245 246 247
          expires_in = options && options[:expires_in]
          raise ":expires_in must be a number" if expires_in && !expires_in.is_a?(Numeric)
          expires_in || 0
248 249
        end

J
José Valim 已提交
250
        def instrument(operation, key, options)
251
          log(operation, key, options)
J
José Valim 已提交
252

253 254 255
          if self.class.instrument
            payload = { :key => key }
            payload.merge!(options) if options.is_a?(Hash)
256
            ActiveSupport::Notifications.instrument("active_support.cache_#{operation}", payload){ yield }
257 258 259
          else
            yield
          end
J
José Valim 已提交
260 261
        end

262
        def log(operation, key, options)
263 264
          return unless logger && !silence?
          logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}")
265 266 267 268
        end
    end
  end
end