require 'active_support/concern'
require 'active_support/cache'
module Cell
module Caching
extend ActiveSupport::Concern
module ClassMethods
# Activate caching for the state state. If no other options are passed
# the view will be cached forever.
#
# You may pass a Proc or a Symbol as cache expiration version_proc.
# This method is called every time the state is rendered, and is expected to return a
# Hash containing the cache key ingredients.
#
# Additional options will be passed directly to the cache store when caching the state.
# Useful for simply setting a TTL for a cached state.
# Note that you may omit the version_proc.
#
#
# Example:
# class CachingCell < ::Cell::Base
# cache :versioned_cached_state, Proc.new{ {:version => 0} }
# would result in the complete cache key
# cells/CachingCell/versioned_cached_state/version=0
#
# If you provide a symbol, you can access the cell instance directly in the versioning
# method:
#
# class CachingCell < ::Cell::Base
# cache :cached_state, :my_cache_version
#
# def my_cache_version
# { :user => current_user.id,
# :item_id => params[:item] }
# }
# end
# results in a very specific cache key, for customized caching:
# cells/CachingCell/cached_state/user=18/item_id=1
#
# You may also set a TTL only, e.g. when using the memcached store:
#
# cache :cached_state, :expires_in => 3.minutes
#
# Or use both, having a versioning proc and a TTL expiring the state as a fallback
# after a certain amount of time.
#
# cache :cached_state, Proc.new { {:version => 0} }, :expires_in => 10.minutes
#--
### TODO: implement for string, nil.
### DISCUSS: introduce return method #sweep ? so the Proc can explicitly
### delegate re-rendering to the outside.
#--
def cache(state, version_proc=nil, cache_opts={})
if version_proc.is_a?(Hash)
cache_opts = version_proc
version_proc = nil
end
version_procs[state] = version_proc
cache_options[state] = cache_opts
end
def version_procs
@version_procs ||= {}
end
def cache_options
@cache_options ||= {}
end
def cache_store #:nodoc:
::ActionController::Base.cache_store
end
def cache_key_for(cell_class, state, args = {}) #:nodoc:
key_pieces = [cell_class, state]
args.collect{|a,b| [a.to_s, b]}.sort.each{ |k,v| key_pieces << "#{k}=#{v}" }
key = key_pieces.join('/')
::ActiveSupport::Cache.expand_cache_key(key, :cells)
end
def expire_cache_key(key, opts=nil)
cache_store.delete(key, opts)
end
def cache_configured?
::ActionController::Base.cache_configured?
end
end
def render_state(state)
return super(state) unless state_cached?(state)
key = cache_key(state, call_version_proc_for_state(state))
options = self.class.cache_options[state]
self.class.cache_store.fetch(key, options) do
super(state)
end
end
# Call the versioning Proc for the respective state.
def call_version_proc_for_state(state)
version_proc = self.class.version_procs[state]
return {} unless version_proc # call to #cache was without any args.
return version_proc.call(self) if version_proc.kind_of?(Proc)
send(version_proc)
end
def cache_key(state, args = {}) #:nodoc:
self.class.cache_key_for(self.cell_name, state, args)
end
def state_cached?(state)
self.class.version_procs.has_key?(state)
end
end
end