Finished documentation for caching

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@426 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 e59f1b52
*SVN*
* Added an extensive caching module that offers three levels of granularity (page, action, fragment) and a variety of stores.
Read more in ActionController::Caching.
* Added the option of passing a block to ActiveRecordHelper#form in order to add more to the auto-generated form #469 [dom@sisna.com]
form("entry", :action => "sign") do |form|
......
require 'fileutils'
module ActionController #:nodoc:
# To turn off all caching and sweeping, set Base.perform_caching = false.
# 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.
module Caching
def self.append_features(base)
def self.append_features(base) #:nodoc:
super
base.send(:include, Pages, Actions, Fragments, Sweeping)
base.class_eval do
......@@ -41,6 +46,11 @@ def self.append_features(base)
#
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
# expired.
#
# == 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".
module Pages
def self.append_features(base) #:nodoc:
super
......@@ -52,6 +62,16 @@ def self.append_features(base) #:nodoc:
end
module ClassMethods
# 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))
logger.info "Expired page: #{path}" unless logger.nil?
end
# Manually cache the +content+ in the key determined by +path+. Example:
# cache_page "I'm the cached content", "/lists/show"
def cache_page(content, path)
return unless perform_caching
FileUtils.makedirs(File.dirname(page_cache_path(path)))
......@@ -59,12 +79,8 @@ def cache_page(content, path)
logger.info "Cached page: #{path}" unless logger.nil?
end
def expire_page(path)
return unless perform_caching
File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
logger.info "Expired page: #{path}" unless logger.nil?
end
# 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.
def caches_page(*actions)
return unless perform_caching
actions.each do |action|
......@@ -72,15 +88,18 @@ def caches_page(*actions)
end
end
def page_cache_path(path)
if path[-1,1] == '/'
page_cache_directory + path + '/index'
else
page_cache_directory + path
private
def page_cache_path(path)
if path[-1,1] == '/'
page_cache_directory + path + '/index'
else
page_cache_directory + path
end
end
end
end
# Expires the page that was cached with the +options+ as a key. Example:
# expire_page :controller => "lists", :action => "show"
def expire_page(options = {})
return unless perform_caching
if options[:action].is_a?(Array)
......@@ -92,23 +111,18 @@ def expire_page(options = {})
end
end
# Expires more than one page at the time. Example:
# expire_pages(
# { :controller => "lists", :action => "public", :id => list_id },
# { :controller => "lists", :action => "show", :id => list_id }
# )
def expire_pages(*options)
options.each { |option| expire_page(option) }
end
# 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 })))
end
def caching_allowed
!@request.method.post? and (@request.parameters.reject {|k, v| ['id', 'action', 'controller'].include?(k)}).empty?
end
private
def caching_allowed
!@request.method.post? && (@request.parameters.reject {|k, v| ['id', 'action', 'controller'].include?(k)}).empty?
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,
......@@ -135,7 +149,7 @@ def self.append_features(base) #:nodoc:
base.send(:attr_accessor, :rendered_action_cache)
end
module ClassMethods
module ClassMethods #:nodoc:
def caches_action(*actions)
return unless perform_caching
around_filter(ActionCacheFilter.new(*actions))
......@@ -198,7 +212,31 @@ def after(controller)
#
# == Fragment stores
#
# TO BE WRITTEN...
# 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")
module Fragments
def self.append_features(base) #:nodoc:
super
......@@ -246,41 +284,41 @@ def expire_fragment(name, options = {})
logger.info "Expired fragment: #{name}" unless logger.nil?
end
class MemoryStore
class MemoryStore #:nodoc:
def initialize
@data = { }
@data, @mutex = { }, Mutex.new
end
def read(name, options = {}) #:nodoc:
begin
@data[name]
@mutex.synchronize { @data[name] }
rescue
nil
end
end
def write(name, value, options = {}) #:nodoc:
@data[name] = value
@mutex.synchronize { @data[name] = value }
end
def delete(name, options = {}) #:nodoc:
@data.delete(name)
@mutex.synchronize { @data.delete(name) }
end
end
class DRbStore < MemoryStore
class DRbStore < MemoryStore #:nodoc:
def initialize(address = 'druby://localhost:9192')
@data = DRbObject.new(nil, address)
end
end
class MemCacheStore < MemoryStore
class MemCacheStore < MemoryStore #:nodoc:
def initialize(address = 'localhost')
@data = MemCache.new(address)
end
end
class FileStore
class FileStore #:nodoc:
def initialize(cache_path)
@cache_path = cache_path
end
......@@ -317,38 +355,38 @@ def ensure_cache_path(path)
end
end
module Sweeping #:nodoc:
# 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
def self.append_features(base) #:nodoc:
super
base.extend(ClassMethods)
end
# 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 ClassMethods
module ClassMethods #:nodoc:
def cache_sweeper(*sweepers)
return unless perform_caching
configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册