0
+ @@nil_sentinel = :_nil
0
+ config = ActsAsCached::Config.class_config[cache_name] ||= {}
0
+ ActsAsCached::Config.class_config[name] ||= config.dup
0
+ cache_config[:options] ||= {}
0
+ options = args.last.is_a?(Hash) ? args.pop : {}
0
+ # head off to get_caches if we were passed multiple cache_ids
0
+ return get_caches(args, options)
0
+ if (item = fetch_cache(cache_id)).nil?
0
+ set_cache(cache_id, block_given? ? yield : fetch_cachable_data(cache_id), options[:ttl])
0
+ @@nil_sentinel == item ? nil : item
0
+ # This method accepts an array of cache_ids which it will use to call
0
+ # get_multi on your cache store. Any misses will be fetched and saved to
0
+ # the cache, and a hash keyed by cache_id will ultimately be returned.
0
+ # If your cache store does not support #get_multi an exception will be raised.
0
+ raise NoGetMulti unless cache_store.respond_to? :get_multi
0
+ options = args.last.is_a?(Hash) ? args.pop : {}
0
+ cache_ids = args.flatten.map(&:to_s)
0
+ keys = cache_keys(cache_ids)
0
+ # Map memcache keys to object cache_ids in { memcache_key => object_id } format
0
+ keys_map = Hash[*keys.zip(cache_ids).flatten]
0
+ # Call get_multi and figure out which keys were missed based on what was a hit
0
+ hits = cache_store(:get_multi, *keys) || {}
0
+ # Misses can take the form of key => nil
0
+ hits.delete_if { |key, value| value.nil? }
0
+ misses = keys - hits.keys
0
+ hits.each { |k, v| hits[k] = nil if v == @@nil_sentinel }
0
+ # Return our hash if there are no misses
0
+ return hits.values.index_by(&:cache_id) if misses.empty?
0
+ # Find any missed records
0
+ needed_ids = keys_map.values_at(*misses)
0
+ missed_records = Array(fetch_cachable_data(needed_ids))
0
+ # Cache the missed records
0
+ missed_records.each { |missed_record| missed_record.set_cache(options[:ttl]) }
0
+ # Return all records as a hash indexed by object cache_id
0
+ (hits.values + missed_records).index_by(&:cache_id)
0
+ def set_cache(cache_id, value, ttl = nil)
0
+ returning(value) do |v|
0
+ v = @@nil_sentinel if v.nil?
0
+ cache_store(:set, cache_key(cache_id), v, ttl || cache_config[:ttl] || 1500)
0
+ def expire_cache(cache_id = nil)
0
+ cache_store(:delete, cache_key(cache_id))
0
+ alias :clear_cache :expire_cache
0
+ def reset_cache(cache_id = nil)
0
+ set_cache(cache_id, fetch_cachable_data(cache_id))
0
+ # Encapsulates the pattern of writing custom cache methods
0
+ # which do nothing but wrap custom finders.
0
+ # => Story.caches(:find_popular)
0
+ # def self.cached_find_popular
0
+ # get_cache(:find_popular) { find_popular }
0
+ # The method also accepts both a :ttl and/or a :with key.
0
+ # Obviously the :ttl value controls how long this method will
0
+ # stay cached, while the :with key's value will be passed along
0
+ # to the method. The hash of the :with key will be stored with the key,
0
+ # making two near-identical #caches calls with different :with values utilize
0
+ # => Story.caches(:find_popular, :with => :today)
0
+ # def self.cached_find_popular
0
+ # get_cache("find_popular:today") { find_popular(:today) }
0
+ # If your target method accepts multiple parameters, pass :withs an array.
0
+ # => Story.caches(:find_popular, :withs => [ :one, :two ])
0
+ # def self.cached_find_popular
0
+ # get_cache("find_popular:onetwo") { find_popular(:one, :two) }
0
+ def caches(method, options = {})
0
+ if options.keys.include?(:with)
0
+ with = options.delete(:with)
0
+ get_cache("#{method}:#{with}", options) { send(method, with) }
0
+ elsif withs = options.delete(:withs)
0
+ get_cache("#{method}:#{withs}", options) { send(method, *withs) }
0
+ get_cache(method, options) { send(method) }
0
+ def cached?(cache_id = nil)
0
+ fetch_cache(cache_id).nil? ? false : true
0
+ alias :is_cached? :cached?
0
+ def fetch_cache(cache_id)
0
+ return if ActsAsCached.config[:skip_gets]
0
+ autoload_missing_constants do
0
+ cache_store(:get, cache_key(cache_id))
0
+ def fetch_cachable_data(cache_id = nil)
0
+ finder = cache_config[:finder] || :find
0
+ return send(finder) unless cache_id
0
+ args << cache_options.dup unless cache_options.blank?
0
+ cache_store(:namespace)
0
+ # Memcache-client automatically prepends the namespace, plus a colon, onto keys, so we take that into account for the max key length.
0
+ key_size = cache_config[:key_size] || 250
0
+ @max_key_length ||= cache_namespace ? (key_size - cache_namespace.length - 1) : key_size
0
+ @cache_name ||= respond_to?(:base_class) ? base_class.name : name
0
+ def cache_keys(*cache_ids)
0
+ cache_ids.flatten.map { |cache_id| cache_key(cache_id) }
0
+ def cache_key(cache_id)
0
+ [cache_name, cache_config[:version], cache_id].compact.join(':').gsub(' ', '_').first(max_key_length)
0
+ def cache_store(method = nil, *args)
0
+ return cache_config[:store] unless method
0
+ load_constants = %w( get get_multi ).include? method.to_s
0
+ swallow_or_raise_cache_errors(load_constants) do
0
+ cache_config[:store].send(method, *args)
0
+ def swallow_or_raise_cache_errors(load_constants = false, &block)
0
+ load_constants ? autoload_missing_constants(&block) : yield
0
+ rescue NoMethodError, ArgumentError, MemCache::MemCacheError => error
0
+ if ActsAsCached.config[:raise_errors]
0
+ RAILS_DEFAULT_LOGGER.debug "MemCache Error: #{error.message}" rescue nil
0
+ rescue TypeError => error
0
+ if error.to_s.include? 'Proc'
0
+ raise MarshalError, "Most likely an association callback defined with a Proc is triggered, see http://ar.rubyonrails.com/classes/ActiveRecord/Associations/ClassMethods.html (Association Callbacks) for details on converting this to a method based callback"
0
+ def autoload_missing_constants
0
+ rescue ArgumentError, MemCache::MemCacheError => error
0
+ lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }