2005-01-08 18:32:11 -05:00
|
|
|
require 'fileutils'
|
|
|
|
|
|
|
|
module ActionController #:nodoc:
|
2005-01-15 17:16:41 -05:00
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# You can read more about each approach and the sweeping assistance by clicking the modules below.
|
|
|
|
#
|
|
|
|
# Note: To turn off all caching and sweeping, set Base.perform_caching = false.
|
2005-01-11 17:03:27 -05:00
|
|
|
module Caching
|
2005-01-15 17:16:41 -05:00
|
|
|
def self.append_features(base) #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
super
|
2005-01-10 13:20:58 -05:00
|
|
|
base.send(:include, Pages, Actions, Fragments, Sweeping)
|
2005-01-11 17:03:27 -05:00
|
|
|
base.class_eval do
|
|
|
|
@@perform_caching = true
|
|
|
|
cattr_accessor :perform_caching
|
|
|
|
end
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
|
|
|
# can serve without going through the Action Pack. This can be as much as 100 times faster than going the process of dynamically
|
|
|
|
# generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
|
|
|
|
# are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
|
|
|
|
# for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
|
|
|
|
#
|
2005-02-07 09:15:53 -05:00
|
|
|
# Specifying which actions to cache is done through the <tt>caches</tt> class method:
|
2005-01-08 18:32:11 -05:00
|
|
|
#
|
|
|
|
# class WeblogController < ActionController::Base
|
2005-01-10 13:20:58 -05:00
|
|
|
# caches_page :show, :new
|
2005-01-08 18:32:11 -05:00
|
|
|
# end
|
|
|
|
#
|
|
|
|
# This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
|
|
|
|
# generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to
|
|
|
|
# the Action Pack to generate it.
|
|
|
|
#
|
|
|
|
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
|
|
|
# is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
|
|
|
|
#
|
|
|
|
# class WeblogController < ActionController::Base
|
|
|
|
# def update
|
|
|
|
# List.update(@params["list"]["id"], @params["list"])
|
|
|
|
# expire_page :action => "show", :id => @params["list"]["id"]
|
|
|
|
# redirect_to :action => "show", :id => @params["list"]["id"]
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2005-01-10 13:20:58 -05:00
|
|
|
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
|
|
|
# expired.
|
2005-01-15 17:16:41 -05:00
|
|
|
#
|
|
|
|
# == Setting the cache directory
|
|
|
|
#
|
|
|
|
# The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
|
|
|
|
# For Rails, this directory has already been set to RAILS_ROOT + "/public".
|
2005-01-08 18:32:11 -05:00
|
|
|
module Pages
|
2005-01-10 14:18:37 -05:00
|
|
|
def self.append_features(base) #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
super
|
|
|
|
base.extend(ClassMethods)
|
|
|
|
base.class_eval do
|
|
|
|
@@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
|
|
|
|
cattr_accessor :page_cache_directory
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
module ClassMethods
|
2005-01-15 17:16:41 -05:00
|
|
|
# Expires the page that was cached with the +path+ as a key. Example:
|
|
|
|
# expire_page "/lists/show"
|
|
|
|
def expire_page(path)
|
|
|
|
return unless perform_caching
|
|
|
|
File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
|
2005-02-20 20:09:15 -05:00
|
|
|
logger.info "Expired page: #{page_cache_file(path)}" unless logger.nil?
|
2005-01-15 17:16:41 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Manually cache the +content+ in the key determined by +path+. Example:
|
|
|
|
# cache_page "I'm the cached content", "/lists/show"
|
2005-01-08 18:32:11 -05:00
|
|
|
def cache_page(content, path)
|
2005-01-11 17:03:27 -05:00
|
|
|
return unless perform_caching
|
2005-01-15 08:22:58 -05:00
|
|
|
FileUtils.makedirs(File.dirname(page_cache_path(path)))
|
|
|
|
File.open(page_cache_path(path), "w+") { |f| f.write(content) }
|
2005-02-20 20:09:15 -05:00
|
|
|
logger.info "Cached page: #{page_cache_file(path)}" unless logger.nil?
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
|
|
|
# matches the triggering url.
|
2005-01-10 13:20:58 -05:00
|
|
|
def caches_page(*actions)
|
2005-01-11 17:03:27 -05:00
|
|
|
return unless perform_caching
|
2005-01-08 18:32:11 -05:00
|
|
|
actions.each do |action|
|
|
|
|
class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
|
|
|
|
end
|
|
|
|
end
|
2005-01-15 08:22:58 -05:00
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
private
|
2005-02-20 20:09:15 -05:00
|
|
|
def page_cache_file(path)
|
2005-02-28 20:44:50 -05:00
|
|
|
name = ((path.empty? || path == "/") ? "/index" : path)
|
|
|
|
name << '.html' unless path.split('/').last.include? '.'
|
|
|
|
return name
|
2005-02-20 20:09:15 -05:00
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
def page_cache_path(path)
|
2005-02-20 20:09:15 -05:00
|
|
|
page_cache_directory + page_cache_file(path)
|
2005-01-15 08:22:58 -05:00
|
|
|
end
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
# Expires the page that was cached with the +options+ as a key. Example:
|
|
|
|
# expire_page :controller => "lists", :action => "show"
|
2005-01-08 18:32:11 -05:00
|
|
|
def expire_page(options = {})
|
2005-01-11 17:03:27 -05:00
|
|
|
return unless perform_caching
|
2005-01-10 13:20:58 -05:00
|
|
|
if options[:action].is_a?(Array)
|
|
|
|
options[:action].dup.each do |action|
|
|
|
|
self.class.expire_page(url_for(options.merge({ :only_path => true, :action => action })))
|
|
|
|
end
|
|
|
|
else
|
|
|
|
self.class.expire_page(url_for(options.merge({ :only_path => true })))
|
|
|
|
end
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
# 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"
|
2005-01-08 18:32:11 -05:00
|
|
|
def cache_page(content = nil, options = {})
|
2005-01-15 08:22:58 -05:00
|
|
|
return unless perform_caching && caching_allowed
|
2005-01-08 18:32:11 -05:00
|
|
|
self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true })))
|
|
|
|
end
|
2005-01-15 08:22:58 -05:00
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
private
|
|
|
|
def caching_allowed
|
2005-02-20 12:18:59 -05:00
|
|
|
!@request.post?
|
2005-01-15 17:16:41 -05:00
|
|
|
end
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
# 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
|
|
|
|
# allows for authentication and other restrictions on whether someone are supposed to see the cache. Example:
|
|
|
|
#
|
|
|
|
# class ListsController < ApplicationController
|
|
|
|
# before_filter :authenticate, :except => :public
|
|
|
|
# caches_page :public
|
|
|
|
# caches_action :show, :feed
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
|
|
|
|
# show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
module Actions
|
2005-01-10 14:18:37 -05:00
|
|
|
def self.append_features(base) #:nodoc:
|
2005-01-10 13:20:58 -05:00
|
|
|
super
|
|
|
|
base.extend(ClassMethods)
|
|
|
|
base.send(:attr_accessor, :rendered_action_cache)
|
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
module ClassMethods #:nodoc:
|
2005-01-10 13:20:58 -05:00
|
|
|
def caches_action(*actions)
|
2005-01-11 17:03:27 -05:00
|
|
|
return unless perform_caching
|
2005-01-10 13:20:58 -05:00
|
|
|
around_filter(ActionCacheFilter.new(*actions))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def expire_action(options = {})
|
2005-01-11 17:03:27 -05:00
|
|
|
return unless perform_caching
|
2005-01-10 21:11:24 -05:00
|
|
|
if options[:action].is_a?(Array)
|
|
|
|
options[:action].dup.each do |action|
|
|
|
|
expire_fragment(url_for(options.merge({ :action => action })).split("://").last)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
expire_fragment(url_for(options).split("://").last)
|
|
|
|
end
|
2005-01-10 13:20:58 -05:00
|
|
|
end
|
|
|
|
|
2005-01-10 14:18:37 -05:00
|
|
|
class ActionCacheFilter #:nodoc:
|
2005-01-10 13:20:58 -05:00
|
|
|
def initialize(*actions)
|
|
|
|
@actions = actions
|
|
|
|
end
|
|
|
|
|
|
|
|
def before(controller)
|
|
|
|
return unless @actions.include?(controller.action_name.intern)
|
|
|
|
if cache = controller.read_fragment(controller.url_for.split("://").last)
|
|
|
|
controller.rendered_action_cache = true
|
|
|
|
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)
|
|
|
|
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>
|
2005-01-20 12:26:14 -05:00
|
|
|
# <% cache do %>
|
2005-01-10 13:20:58 -05:00
|
|
|
# All the topics in the system:
|
|
|
|
# <%= render_collection_of_partials "topic", 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
|
|
|
|
# qualify the name of the action used with something like:
|
|
|
|
#
|
2005-01-20 12:26:14 -05:00
|
|
|
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
2005-01-10 13:20:58 -05:00
|
|
|
#
|
|
|
|
# That would result in a name such as "/topics/list/all_topics", which wouldn't conflict with any action cache and neither with another
|
|
|
|
# fragment using a different suffix. Note that the URL doesn't have to really exist or be callable. We're just using the url_for system
|
|
|
|
# to generate unique cache names that we can refer to later for expirations. The expiration call for this example would be
|
|
|
|
# <tt>expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")</tt>.
|
|
|
|
#
|
|
|
|
# == Fragment stores
|
|
|
|
#
|
2005-01-15 17:16:41 -05:00
|
|
|
# In order to use the fragment caching, you need to designate where the caches should be stored. This is done by assigning a fragment store
|
|
|
|
# of which there are four different kinds:
|
|
|
|
#
|
|
|
|
# * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and share the fragments for
|
|
|
|
# all the web server processes running off the same application directory.
|
|
|
|
# * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
|
|
|
|
# own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
|
|
|
|
# up a lot of memory since each process keeps all the caches in memory.
|
|
|
|
# * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
|
|
|
|
# around for all processes, but requires that you run and manage a separate DRb process.
|
|
|
|
# * MemCachedStore: Works like DRbStore, but uses Danga's MemCached instead.
|
|
|
|
#
|
|
|
|
# Configuration examples (MemoryStore is the default):
|
|
|
|
#
|
|
|
|
# ActionController::Base.fragment_cache_store =
|
|
|
|
# ActionController::Caching::Fragments::MemoryStore.new
|
|
|
|
#
|
|
|
|
# ActionController::Base.fragment_cache_store =
|
|
|
|
# ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")
|
|
|
|
#
|
|
|
|
# ActionController::Base.fragment_cache_store =
|
|
|
|
# ActionController::Caching::Fragments::DRbStore.new("druby://localhost:9192")
|
|
|
|
#
|
|
|
|
# ActionController::Base.fragment_cache_store =
|
|
|
|
# ActionController::Caching::Fragments::FileStore.new("localhost")
|
2005-01-08 18:32:11 -05:00
|
|
|
module Fragments
|
2005-01-10 14:18:37 -05:00
|
|
|
def self.append_features(base) #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
super
|
|
|
|
base.class_eval do
|
2005-01-20 09:12:53 -05:00
|
|
|
@@fragment_cache_store = MemoryStore.new
|
2005-01-08 18:32:11 -05:00
|
|
|
cattr_accessor :fragment_cache_store
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
# Called by CacheHelper#cache
|
2005-01-20 12:54:06 -05:00
|
|
|
def cache_erb_fragment(block, name = {}, options = {})
|
2005-01-20 12:26:14 -05:00
|
|
|
unless perform_caching then block.call; return end
|
2005-01-11 17:03:27 -05:00
|
|
|
|
2005-01-20 12:54:06 -05:00
|
|
|
buffer = eval("_erbout", block.binding)
|
2005-01-10 13:20:58 -05:00
|
|
|
|
|
|
|
if cache = read_fragment(name, options)
|
2005-01-08 18:32:11 -05:00
|
|
|
buffer.concat(cache)
|
|
|
|
else
|
|
|
|
pos = buffer.length
|
2005-01-20 12:26:14 -05:00
|
|
|
block.call
|
2005-01-10 13:20:58 -05:00
|
|
|
write_fragment(name, buffer[pos..-1], options)
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
end
|
2005-01-10 13:20:58 -05:00
|
|
|
|
|
|
|
def write_fragment(name, content, options = {})
|
2005-01-10 21:11:24 -05:00
|
|
|
name = url_for(name).split("://").last if name.is_a?(Hash)
|
2005-01-10 13:20:58 -05:00
|
|
|
fragment_cache_store.write(name, content, options)
|
|
|
|
logger.info "Cached fragment: #{name}" unless logger.nil?
|
|
|
|
content
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_fragment(name, options = {})
|
2005-01-10 21:11:24 -05:00
|
|
|
name = url_for(name).split("://").last if name.is_a?(Hash)
|
2005-01-10 13:20:58 -05:00
|
|
|
if cache = fragment_cache_store.read(name, options)
|
|
|
|
logger.info "Fragment hit: #{name}" unless logger.nil?
|
|
|
|
cache
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def expire_fragment(name, options = {})
|
2005-01-10 21:11:24 -05:00
|
|
|
name = url_for(name).split("://").last if name.is_a?(Hash)
|
2005-01-10 13:20:58 -05:00
|
|
|
fragment_cache_store.delete(name, options)
|
|
|
|
logger.info "Expired fragment: #{name}" unless logger.nil?
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
class MemoryStore #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
def initialize
|
2005-01-15 17:16:41 -05:00
|
|
|
@data, @mutex = { }, Mutex.new
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
2005-01-15 17:16:41 -05:00
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
def read(name, options = {}) #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
begin
|
2005-01-15 17:16:41 -05:00
|
|
|
@mutex.synchronize { @data[name] }
|
2005-01-08 18:32:11 -05:00
|
|
|
rescue
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
def write(name, value, options = {}) #:nodoc:
|
2005-01-15 17:16:41 -05:00
|
|
|
@mutex.synchronize { @data[name] = value }
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
def delete(name, options = {}) #:nodoc:
|
2005-01-15 17:16:41 -05:00
|
|
|
@mutex.synchronize { @data.delete(name) }
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
class DRbStore < MemoryStore #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
def initialize(address = 'druby://localhost:9192')
|
2005-02-20 18:08:07 -05:00
|
|
|
@data, @mutex = DRbObject.new(nil, address), Mutex.new
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
class MemCacheStore < MemoryStore #:nodoc:
|
2005-01-10 19:30:53 -05:00
|
|
|
def initialize(address = 'localhost')
|
2005-02-20 18:08:07 -05:00
|
|
|
@data, @mutex = MemCache.new(address), Mutex.new
|
2005-01-10 19:30:53 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
class FileStore #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
def initialize(cache_path)
|
|
|
|
@cache_path = cache_path
|
|
|
|
end
|
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
def write(name, value, options = {}) #:nodoc:
|
|
|
|
begin
|
|
|
|
ensure_cache_path(File.dirname(real_file_path(name)))
|
|
|
|
File.open(real_file_path(name), "w+") { |f| f.write(value) }
|
|
|
|
rescue => e
|
|
|
|
Base.logger.info "Couldn't create cache directory: #{name} (#{e.message})" unless Base.logger.nil?
|
|
|
|
end
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
def read(name, options = {}) #:nodoc:
|
2005-01-08 18:32:11 -05:00
|
|
|
begin
|
2005-01-10 13:20:58 -05:00
|
|
|
IO.read(real_file_path(name))
|
2005-01-08 18:32:11 -05:00
|
|
|
rescue
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2005-01-10 13:20:58 -05:00
|
|
|
def delete(name, options) #:nodoc:
|
|
|
|
File.delete(real_file_path(name)) if File.exist?(real_file_path(name))
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
2005-01-10 13:20:58 -05:00
|
|
|
def real_file_path(name)
|
|
|
|
"#{@cache_path}/#{name}"
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
2005-01-10 13:20:58 -05:00
|
|
|
|
2005-01-08 18:32:11 -05:00
|
|
|
def ensure_cache_path(path)
|
|
|
|
FileUtils.makedirs(path) unless File.exists?(path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2005-01-10 13:20:58 -05:00
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
# 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
|
|
|
|
# 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) }
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# The sweeper is assigned on the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
|
|
|
|
#
|
|
|
|
# class ListsController < ApplicationController
|
|
|
|
# caches_action :index, :show, :public, :feed
|
|
|
|
# cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# In the example above, four actions are cached and three actions are responsible of expiring those caches.
|
|
|
|
module Sweeping
|
2005-01-10 13:20:58 -05:00
|
|
|
def self.append_features(base) #:nodoc:
|
|
|
|
super
|
|
|
|
base.extend(ClassMethods)
|
|
|
|
end
|
|
|
|
|
2005-01-15 17:16:41 -05:00
|
|
|
module ClassMethods #:nodoc:
|
2005-01-10 13:20:58 -05:00
|
|
|
def cache_sweeper(*sweepers)
|
2005-01-11 17:03:27 -05:00
|
|
|
return unless perform_caching
|
2005-01-10 13:20:58 -05:00
|
|
|
configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
|
|
|
|
sweepers.each do |sweeper|
|
|
|
|
observer(sweeper)
|
|
|
|
after_filter(Object.const_get(Inflector.classify(sweeper)).instance, :only => configuration[:only])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2005-01-08 18:32:11 -05:00
|
|
|
end
|
2005-01-15 08:22:58 -05:00
|
|
|
end
|