Skip to content

Commit

Permalink
Moved the caching stores from ActionController::Caching::Fragments::*…
Browse files Browse the repository at this point in the history
… to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [DHH] Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [DHH] All fragment cache keys are now by default prefixed with the 'views/' namespace [DHH] Added ActiveRecord::Base.cache_key to make it easier to cache Active Records in combination with the new ActiveSupport::Cache::* libraries [DHH] Added ActiveSupport::Gzip.decompress/compress(source) as an easy wrapper for Zlib [Tobias Luetke] Included MemCache-Client to make the improved ActiveSupport::Cache::MemCacheStore work out of the box [Bob Cottrell, Eric Hodel] Added config.cache_store to environment options to control the default cache store (default is FileStore if tmp/cache is present, otherwise MemoryStore is used) [DHH]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8546 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
dhh committed Jan 3, 2008
1 parent 2885535 commit 2a9ad9c
Show file tree
Hide file tree
Showing 30 changed files with 1,882 additions and 788 deletions.
6 changes: 6 additions & 0 deletions actionpack/CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
*SVN*

* All fragment cache keys are now by default prefixed with the "views/" namespace [DHH]

* Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [DHH]

* Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [DHH]

* Made fragment caching in views work for rjs and builder as well #6642 [zsombor]

* Fixed rendering of partials with layout when done from site layout #9209 [antramm]
Expand Down
2 changes: 1 addition & 1 deletion actionpack/README
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ A short rundown of the major features:

class WeblogController < ActionController::Base
before_filter :authenticate, :cache, :audit
after_filter { |c| c.response.body = GZip::compress(c.response.body) }
after_filter { |c| c.response.body = Gzip::compress(c.response.body) }
after_filter LocalizeFilter

def index
Expand Down
718 changes: 40 additions & 678 deletions actionpack/lib/action_controller/caching.rb

Large diffs are not rendered by default.

148 changes: 148 additions & 0 deletions actionpack/lib/action_controller/caching/actions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
require 'set'

module ActionController #:nodoc:
module Caching
# 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 is allowed 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.
#
# 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>.
#
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
# caches_page :public
# caches_action :show, :cache_path => { :project => 1 }
# caches_action :show, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ?
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
# controller.send(:list_url, c.params[:id]) }
# end
module Actions
def self.included(base) #:nodoc:
base.extend(ClassMethods)
base.class_eval do
attr_accessor :rendered_action_cache, :action_cache_path
alias_method_chain :protected_instance_variables, :action_caching
end
end

module ClassMethods
# Declares that +actions+ should be cached.
# See ActionController::Caching::Actions for details.
def caches_action(*actions)
return unless cache_configured?
around_filter(ActionCacheFilter.new(*actions))
end
end

protected
def protected_instance_variables_with_action_caching
protected_instance_variables_without_action_caching + %w(@action_cache_path)
end

def expire_action(options = {})
return unless cache_configured?

if options[:action].is_a?(Array)
options[:action].dup.each do |action|
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
end
else
expire_fragment(ActionCachePath.path_for(self, options))
end
end

class ActionCacheFilter #:nodoc:
def initialize(*actions, &block)
@options = actions.extract_options!
@actions = Set.new(actions)
end

def before(controller)
return unless @actions.include?(controller.action_name.intern)

cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))

if cache = controller.read_fragment(cache_path.path)
controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension)
controller.send!(:render_for_text, cache)
false
else
controller.action_cache_path = cache_path
end
end

def after(controller)
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller)
controller.write_fragment(controller.action_cache_path.path, controller.response.body)
end

private
def set_content_type!(controller, extension)
controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension
end

def path_options_for(controller, options)
((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {}
end

def caching_allowed(controller)
controller.request.get? && controller.response.headers['Status'].to_i == 200
end
end

class ActionCachePath
attr_reader :path, :extension

class << self
def path_for(controller, options)
new(controller, options).path
end
end

def initialize(controller, options = {})
@extension = extract_extension(controller.request.path)
path = controller.url_for(options).split('://').last
normalize!(path)
add_extension!(path, @extension)
@path = URI.unescape(path)
end

private
def normalize!(path)
path << 'index' if path[-1] == ?/
end

def add_extension!(path, extension)
path << ".#{extension}" if extension
end

def extract_extension(file_path)
# Don't want just what comes after the last '.' to accommodate multi part extensions
# such as tar.gz.
file_path[/^[^.]+\.(.+)$/, 1]
end
end
end
end
end
153 changes: 153 additions & 0 deletions actionpack/lib/action_controller/caching/fragments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
module ActionController #:nodoc:
module Caching
# 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 :partial => "topic", :collection => Topic.find(:all) %>
# <% end %>
#
# This cache will bind to the name of the action that called it, so if this code was part of the view for the topics/list action, you would
# be able to invalidate it using <tt>expire_fragment(:controller => "topics", :action => "list")</tt>.
#
# This default behavior is of limited use if you need to cache multiple fragments per action or if the action itself is cached using
# <tt>caches_action</tt>, so we also have the option to qualify the name of the cached fragment with something like:
#
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
#
# That would result in a name such as "/topics/list/all_topics", avoiding conflicts with the action cache and with any fragments that use a
# different suffix. Note that the URL doesn't have to really exist or be callable - the url_for system is just used to generate unique
# cache names that we can refer to when we need to expire the cache.
#
# The expiration call for this example is:
#
# expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")
module Fragments
def self.included(base) #:nodoc:
base.class_eval do
class << self
def fragment_cache_store=(store_option) #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
self.cache_store = store_option
end

def fragment_cache_store #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
cache_store
end
end

def fragment_cache_store=(store_option) #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store= method is now use cache_store=')
self.cache_store = store_option
end

def fragment_cache_store #:nodoc:
ActiveSupport::Deprecation.warn('The fragment_cache_store method is now use cache_store')
cache_store
end
end
end

# Given a key (as described in <tt>expire_fragment</tt>), returns a key suitable for use in reading,
# writing, or expiring a cached fragment. If the key is a hash, the generated key is the return
# value of url_for on that hash (without the protocol). All keys are prefixed with "views/" and uses
# ActiveSupport::Cache.expand_cache_key for the expansion.
def fragment_cache_key(key)
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end

def fragment_for(block, name = {}, options = nil) #:nodoc:
unless perform_caching then block.call; return end

buffer = yield

if cache = read_fragment(name, options)
buffer.concat(cache)
else
pos = buffer.length
block.call
write_fragment(name, buffer[pos..-1], options)
end
end

# Called by CacheHelper#cache
def cache_rxml_fragment(block, name = {}, options = nil) #:nodoc:
fragment_for(block, name, options) do
eval('xml.target!', block.binding)
end
end

# Called by CacheHelper#cache
def cache_rjs_fragment(block, name = {}, options = nil) #:nodoc:
fragment_for(block, name, options) do
begin
debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false
eval('page.to_s', block.binding)
ensure
ActionView::Base.debug_rjs = debug_mode
end
end
end

# Called by CacheHelper#cache
def cache_erb_fragment(block, name = {}, options = nil) #:nodoc:
fragment_for(block, name, options) do
eval(ActionView::Base.erb_variable, block.binding)
end
end

# Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
def write_fragment(key, content, options = nil)
return unless cache_configured?

key = fragment_cache_key(key)

self.class.benchmark "Cached fragment miss: #{key}" do
cache_store.write(key, content, options)
end

content
end

# Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats)
def read_fragment(key, options = nil)
return unless cache_configured?

key = fragment_cache_key(key)

self.class.benchmark "Cached fragment hit: #{key}" do
cache_store.read(key, 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}
# 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 only supported on caches that can iterate over
# all keys (unlike memcached).
def expire_fragment(key, options = nil)
return unless cache_configured?

key = key.is_a?(Regexp) ? key : fragment_cache_key(key)

if key.is_a?(Regexp)
self.class.benchmark "Expired fragments matching: #{key.source}" do
cache_store.delete_matched(key, options)
end
else
self.class.benchmark "Expired fragment: #{key}" do
cache_store.delete(key, options)
end
end
end
end
end
end
Loading

0 comments on commit 2a9ad9c

Please sign in to comment.