lib/action_controller/caching.rb in actionpack-1.12.5 vs lib/action_controller/caching.rb in actionpack-1.13.0

- old
+ new

@@ -1,6 +1,7 @@ require 'fileutils' +require 'uri' module ActionController #:nodoc: # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment. # @@ -115,28 +116,28 @@ # expire_page :controller => "lists", :action => "show" def expire_page(options = {}) return unless perform_caching if options[:action].is_a?(Array) options[:action].dup.each do |action| - self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true, :action => action }))) + self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action))) end else - self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true }))) + self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true))) end end - # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used + # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used # If no options are provided, the current +options+ for this action is used. Example: # cache_page "I'm the cached content", :controller => "lists", :action => "show" def cache_page(content = nil, options = {}) return unless perform_caching && caching_allowed - self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true }))) + self.class.cache_page(content || response.body, url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format]))) end private def caching_allowed - !@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400 + request.get? && response.headers['Status'].to_i == 200 end end # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching, # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which @@ -153,13 +154,16 @@ # # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both # 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 # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern. + # + # Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt> + # 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 + # as <tt>:action => 'list', :format => :xml</tt>. module Actions - def self.append_features(base) #:nodoc: - super + def self.included(base) #:nodoc: base.extend(ClassMethods) base.send(:attr_accessor, :rendered_action_cache) end module ClassMethods #:nodoc: @@ -171,46 +175,100 @@ def expire_action(options = {}) return unless perform_caching if options[:action].is_a?(Array) options[:action].dup.each do |action| - expire_fragment(url_for(options.merge({ :action => action })).split("://").last) + expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }))) end else - expire_fragment(url_for(options).split("://").last) + expire_fragment(ActionCachePath.path_for(self, options)) end end class ActionCacheFilter #:nodoc: - def initialize(*actions) + def initialize(*actions, &block) @actions = actions end def before(controller) return unless @actions.include?(controller.action_name.intern) - if cache = controller.read_fragment(controller.url_for.split("://").last) + action_cache_path = ActionCachePath.new(controller) + if cache = controller.read_fragment(action_cache_path.path) controller.rendered_action_cache = true + set_content_type!(action_cache_path) controller.send(:render_text, cache) false end end def after(controller) return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache - controller.write_fragment(controller.url_for.split("://").last, controller.response.body) + controller.write_fragment(ActionCachePath.path_for(controller), controller.response.body) end + + private + + def set_content_type!(action_cache_path) + if extention = action_cache_path.extension + content_type = Mime::EXTENSION_LOOKUP[extention] + action_cache_path.controller.response.content_type = content_type.to_s + end + end + end + + class ActionCachePath + attr_reader :controller, :options + + class << self + def path_for(*args, &block) + new(*args).path + end + end + + def initialize(controller, options = {}) + @controller = controller + @options = options + end + + def path + return @path if @path + @path = controller.url_for(options).split('://').last + normalize! + add_extension! + URI.unescape(@path) + end + + def extension + @extension ||= extract_extension(controller.request.path) + end + + private + def normalize! + @path << 'index' if @path.last == '/' + end + + def add_extension! + @path << ".#{extension}" if extension + end + + def extract_extension(file_path) + # Don't want just what comes after the last '.' to accomodate multi part extensions + # such as tar.gz. + file_path[/^[^.]+\.(.+)$/, 1] + end + end end # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like: # # <b>Hello <%= @name %></b> # <% cache do %> # All the topics in the system: - # <%= render_collection_of_partials "topic", Topic.find_all %> + # <%= render :partial => "topic", :collection => Topic.find(:all) %> # <% end %> # # This cache will bind to the name of action that called it. So you would be able to invalidate it using # <tt>expire_fragment(:controller => "topics", :action => "list")</tt> -- if that was the controller/action used. This is not too helpful # if you need to cache multiple fragments per action or if the action itself is cached using <tt>caches_action</tt>. So instead we should @@ -244,12 +302,11 @@ # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory" # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192" # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost" # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter") module Fragments - def self.append_features(base) #:nodoc: - super + def self.included(base) #:nodoc: base.class_eval do @@fragment_cache_store = MemoryStore.new cattr_reader :fragment_cache_store def self.fragment_cache_store=(store_option) @@ -304,11 +361,16 @@ end # Name can take one of three forms: # * String: This would normally take the form of a path like "pages/45/notes" # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 } - # * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes} Ensure you do not specify start and finish in the regex (^$) because the actual filename matched looks like ./cache/filename/path.cache + # * Regexp: Will destroy all the matched fragments, example: + # %r{pages/\d*/notes} + # Ensure you do not specify start and finish in the regex (^$) because + # the actual filename matched looks like ./cache/filename/path.cache + # Regexp expiration is not supported on caches which can't iterate over + # all keys, such as memcached. def expire_fragment(name, options = nil) return unless perform_caching key = fragment_cache_key(name) @@ -325,10 +387,11 @@ # Deprecated -- just call expire_fragment with a regular expression def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc: expire_fragment(matcher, options) end + deprecate :expire_matched_fragments => :expire_fragment class UnthreadedMemoryStore #:nodoc: def initialize #:nodoc: @data = {} @@ -428,11 +491,11 @@ def delete_matched(matcher, options) #:nodoc: search_dir(@cache_path) do |f| if f =~ matcher begin File.delete(f) - rescue Object => e + rescue SystemCallError => e # If there's no cache, then there's nothing to complain about end end end end @@ -491,22 +554,20 @@ # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ] # end # # In the example above, four actions are cached and three actions are responsible for expiring those caches. module Sweeping - def self.append_features(base) #:nodoc: - super + def self.included(base) #:nodoc: base.extend(ClassMethods) end module ClassMethods #:nodoc: def cache_sweeper(*sweepers) return unless perform_caching configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {} sweepers.each do |sweeper| - observer(sweeper) - + ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance if sweeper_instance.is_a?(Sweeper) around_filter(sweeper_instance, :only => configuration[:only]) else @@ -521,10 +582,10 @@ class Sweeper < ActiveRecord::Observer #:nodoc: attr_accessor :controller # ActiveRecord::Observer will mark this class as reloadable even though it should not be. # However, subclasses of ActionController::Caching::Sweeper should be Reloadable - include Reloadable::Subclasses + include Reloadable::Deprecated def before(controller) self.controller = controller callback(:before) end