Skip to content
Browse files

Finished documentation for caching

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@426 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
1 parent e59f1b5 commit bc4156e09e558260cc39a238ca4d17b44208ce2a @dhh dhh committed Jan 15, 2005
Showing with 105 additions and 64 deletions.
  1. +3 −0 actionpack/CHANGELOG
  2. +102 −64 actionpack/lib/action_controller/caching.rb
View
3 actionpack/CHANGELOG
@@ -1,5 +1,8 @@
*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|
View
166 actionpack/lib/action_controller/caching.rb
@@ -1,9 +1,14 @@
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,35 +62,44 @@ 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)))
File.open(page_cache_path(path), "w+") { |f| f.write(content) }
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|
class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
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 : {}

0 comments on commit bc4156e

Please sign in to comment.
Something went wrong with that request. Please try again.