3 module ActionController
#:nodoc:
5 # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
6 # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
7 # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
9 # class ListsController < ApplicationController
10 # before_filter :authenticate, :except => :public
12 # caches_action :index, :show, :feed
15 # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
16 # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
18 # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
19 # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
20 # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
21 # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
23 # Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
24 # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
25 # as <tt>:action => 'list', :format => :xml</tt>.
27 # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
28 # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
30 # And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
32 # Finally, if you are using memcached, you can also pass :expires_in.
34 # class ListsController < ApplicationController
35 # before_filter :authenticate, :except => :public
37 # caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
38 # caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
39 # caches_action :feed, :cache_path => Proc.new { |controller|
40 # controller.params[:user_id] ?
41 # controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) :
42 # controller.send(:list_url, controller.params[:id]) }
45 # If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
48 def self.included(base
) #:nodoc:
49 base
.extend(ClassMethods
)
51 attr_accessor
:rendered_action_cache, :action_cache_path
56 # Declares that +actions+ should be cached.
57 # See ActionController::Caching::Actions for details.
58 def caches_action(*actions
)
59 return unless cache_configured
?
60 options
= actions
.extract_options
!
61 filter_options
= { :only => actions
, :if => options
.delete(:if), :unless => options
.delete(:unless) }
63 cache_filter
= ActionCacheFilter
.new(:layout => options
.delete(:layout), :cache_path => options
.delete(:cache_path), :store_options => options
)
64 around_filter(cache_filter
, filter_options
)
69 def expire_action(options
= {})
70 return unless cache_configured
?
72 if options
[:action].is_a
?(Array
)
73 options
[:action].dup
.each
do |action
|
74 expire_fragment(ActionCachePath
.path_for(self, options
.merge({ :action => action
}), false))
77 expire_fragment(ActionCachePath
.path_for(self, options
, false))
81 class ActionCacheFilter
#:nodoc:
82 def initialize(options
, &block
)
86 def before(controller
)
87 cache_path
= ActionCachePath
.new(controller
, path_options_for(controller
, @options.slice(:cache_path)))
88 if cache
= controller
.read_fragment(cache_path
.path
, @options[:store_options])
89 controller
.rendered_action_cache
= true
90 set_content_type
!(controller
, cache_path
.extension
)
91 options
= { :text => cache
}
92 options
.merge
!(:layout => true) if cache_layout
?
93 controller
.__send__(:render, options
)
96 controller
.action_cache_path
= cache_path
100 def after(controller
)
101 return if controller
.rendered_action_cache
|| !caching_allowed(controller
)
102 action_content
= cache_layout
? ? content_for_layout(controller
) : controller
.response
.body
103 controller
.write_fragment(controller
.action_cache_path
.path
, action_content
, @options[:store_options])
107 def set_content_type
!(controller
, extension
)
108 controller
.response
.content_type
= Mime
::Type.lookup_by_extension(extension
).to_s
if extension
111 def path_options_for(controller
, options
)
112 ((path_options
= options
[:cache_path]).respond_to
?(:call) ? path_options
.call(controller
) : path_options
) || {}
115 def caching_allowed(controller
)
116 controller
.request
.get
? && controller
.response
.headers
['Status'].to_i
== 200
120 @options[:layout] == false
123 def content_for_layout(controller
)
124 controller
.response
.layout
&& controller
.response
.template
.instance_variable_get('@cached_content_for_layout')
128 class ActionCachePath
129 attr_reader
:path, :extension
132 def path_for(controller
, options
, infer_extension
=true)
133 new(controller
, options
, infer_extension
).path
137 # When true, infer_extension will look up the cache path extension from the request's path & format.
138 # This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format.
139 def initialize(controller
, options
= {}, infer_extension
=true)
140 if infer_extension
and options
.is_a
? Hash
141 request_extension
= extract_extension(controller
.request
)
142 options
= options
.reverse_merge(:format => request_extension
)
144 path
= controller
.url_for(options
).split('://').last
147 @extension = request_extension
148 add_extension
!(path
, @extension)
150 @path = URI
.unescape(path
)
155 path
<< 'index' if path
[-1] == ?/
158 def add_extension
!(path
, extension
)
159 path
<< ".#{extension}" if extension
and !path
.ends_with
?(extension
)
162 def extract_extension(request
)
163 # Don't want just what comes after the last '.' to accommodate multi part extensions
165 extension
= request
.path
[/^[^.]+\.(.+)$/, 1]
167 # If there's no extension in the path, check request.format
169 extension
= request
.cache_format