From a2ef9778ce4cb5db18fe6d07e29fa50575ce77f1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 27 Apr 2005 08:14:53 +0000 Subject: [PATCH] Added ActionController::Caching::Sweeper as an improved an easier to use sweeper. Added that Fragments#expire_fragment now accepts as a regular expression as the name thereby deprecating expire_matched_fragments. Fixed that fragments shouldn't use the current host and the path as part of the key like pages does git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1239 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/lib/action_controller/caching.rb | 90 ++++++++++++++------- 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 5b5cdaa2e36cf..b6861cb0815cc 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -256,13 +256,8 @@ def self.append_features(base) #:nodoc: end end - def cache_base_url - @@cache_base_url ||= url_for(:controller => '') - end - def fragment_cache_key(name) - key = name.is_a?(Hash) ? url_for(name) : cache_base_url + name - key.split("://").last + name.is_a?(Hash) ? url_for(name).split("://").last : name end # Called by CacheHelper#cache @@ -297,16 +292,25 @@ def read_fragment(name, options = {}) end 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} def expire_fragment(name, options = {}) key = fragment_cache_key(name) - fragment_cache_store.delete(key, options) - logger.info "Expired fragment: #{key}" unless logger.nil? + + if key.is_a?(Regexp) + fragment_cache_store.delete_matched(key, options) + logger.info "Expired fragments matching: #{key.source}" unless logger.nil? + else + fragment_cache_store.delete(key, options) + logger.info "Expired fragment: #{key}" unless logger.nil? + end end - def expire_matched_fragments(re=Regexp.new('/.*/'), options = {}) - rp = cache_base_url.split("://").last - fragment_cache_store.delete_matched(re, { :root_path => rp }) - logger.info "Expired all fragments matching: #{rp}#{re.source}" unless logger.nil? + # Deprecated -- just call expire_fragment with a regular expression + def expire_matched_fragments(matcher = /.*/, options = {}) #:nodoc: + expire_fragment(matcher, options) end class MemoryStore #:nodoc: @@ -326,9 +330,8 @@ def delete(name, options = {}) #:nodoc: @mutex.synchronize { @data.delete(name) } end - def delete_matched(re, options) #:nodoc: - re = Regexp.new("#{Regexp.escape(options[:root_path])}#{re.source}") - @mutex.synchronize { @data.delete_if { |k,v| k =~ re } } + def delete_matched(matcher, options) #:nodoc: + @mutex.synchronize { @data.delete_if { |k,v| k =~ matcher } } end end @@ -364,10 +367,9 @@ def delete(name, options) #:nodoc: File.delete(real_file_path(name)) if File.exist?(real_file_path(name)) end - def delete_matched(re, options) #:nodoc: - rootPath = real_file_path(options[:root_path]) + def delete_matched(matcher, options) #:nodoc: search_dir(@cache_path).each do |f| - File.delete(f) if f.index(rootPath) == 0 and f =~ re and File.exist?(f) + File.delete(f) if f =~ matcher && File.exist?(f) end end @@ -400,17 +402,14 @@ def search_dir(dir) # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change. # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example: # - # class ListSweeper < ActiveRecord::Observer + # class ListSweeper < ActionController::Caching::Sweeper # observe List, Item # # def after_save(record) - # @list = record.is_a?(List) ? record : record.list - # end - # - # def filter(controller) - # controller.expire_page(:controller => "lists", :action => %w( show public feed ), :id => @list.id) - # controller.expire_action(:controller => "lists", :action => "all") - # @list.shares.each { |share| controller.expire_page(:controller => "lists", :action => "show", :id => share.url_key) } + # list = record.is_a?(List) ? record : record.list + # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id) + # expire_action(:controller => "lists", :action => "all") + # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) } # end # end # @@ -432,12 +431,47 @@ module ClassMethods #:nodoc: def cache_sweeper(*sweepers) return unless perform_caching configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {} - sweepers.each do |sweeper| + sweepers.each do |sweeper| observer(sweeper) - after_filter(Object.const_get(Inflector.classify(sweeper)).instance, :only => configuration[:only]) + + sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance + + if sweeper_instance.is_a?(Sweeper) + around_filter(sweeper_instance, :only => configuration[:only]) + else + after_filter(sweeper_instance, :only => configuration[:only]) + end end end end end + + if defined?("ActiveRecord") + class Sweeper < ActiveRecord::Observer + attr_accessor :controller + + def before(controller) + self.controller = controller + callback(:before) + end + + def after(controller) + callback(:after) + end + + private + def callback(timing) + controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}" + action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}" + + send(controller_callback_method_name) if respond_to?(controller_callback_method_name) + send(action_callback_method_name) if respond_to?(action_callback_method_name) + end + + def method_missing(method, *arguments) + @controller.send(method, *arguments) + end + end + end end end