aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/caching
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller/caching')
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb186
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb76
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb187
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb97
4 files changed, 23 insertions, 523 deletions
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
deleted file mode 100644
index ba96735e56..0000000000
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ /dev/null
@@ -1,186 +0,0 @@
-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 Action Pack. The key benefit of this is
- # that filters run before the cache is served, which allows for
- # authentication and other restrictions on whether someone is allowed
- # to execute such action. Example:
- #
- # class ListsController < ApplicationController
- # before_filter :authenticate, :except => :public
- #
- # caches_page :public
- # caches_action :index, :show
- # end
- #
- # In this example, the +public+ action doesn't require authentication
- # so it's possible to use the faster page caching. On the other hand
- # +index+ and +show+ require authentication. They can still be cached,
- # but we need action caching for them.
- #
- # Action caching uses fragment caching internally and an around
- # filter to do the job. The fragment cache is named according to
- # the host and path of the request. A page that is accessed at
- # <tt>http://david.example.com/lists/show/1</tt> will result in a fragment named
- # <tt>david.example.com/lists/show/1</tt>. This allows the cacher to
- # differentiate between <tt>david.example.com/lists/</tt> and
- # <tt>jamis.example.com/lists/</tt> -- which is a helpful way of assisting
- # the subdomain-as-account-key pattern.
- #
- # Different representations of the same resource, e.g.
- # <tt>http://david.example.com/lists</tt> and
- # <tt>http://david.example.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 modify the default action cache path by passing a
- # <tt>:cache_path</tt> option. This will be passed directly to
- # <tt>ActionCachePath.path_for</tt>. 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.
- #
- # And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
- # proc that specifies when the action should be cached.
- #
- # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time
- # interval (in seconds) to schedule expiration of the cached item.
- #
- # The following example depicts some of the points made above:
- #
- # class ListsController < ApplicationController
- # before_filter :authenticate, :except => :public
- #
- # caches_page :public
- #
- # caches_action :index, :if => Proc.new do
- # !request.format.json? # cache if is not a JSON request
- # end
- #
- # caches_action :show, :cache_path => { :project => 1 },
- # :expires_in => 1.hour
- #
- # caches_action :feed, :cache_path => Proc.new do
- # if params[:user_id]
- # user_list_url(params[:user_id, params[:id])
- # else
- # list_url(params[:id])
- # end
- # end
- # end
- #
- # If you pass <tt>:layout => false</tt>, it will only cache your action
- # content. That's useful when your layout has dynamic information.
- #
- # Warning: If the format of the request is determined by the Accept HTTP
- # header the Content-Type of the cached response could be wrong because
- # no information about the MIME type is stored in the cache key. So, if
- # you first ask for MIME type M in the Accept header, a cache entry is
- # created, and then perform a second request to the same resource asking
- # for a different MIME type, you'd get the content cached for M.
- #
- # The <tt>:format</tt> parameter is taken into account though. The safest
- # way to cache by MIME type is to pass the format in the route.
- module Actions
- extend ActiveSupport::Concern
-
- module ClassMethods
- # Declares that +actions+ should be cached.
- # See ActionController::Caching::Actions for details.
- def caches_action(*actions)
- return unless cache_configured?
- options = actions.extract_options!
- options[:layout] = true unless options.key?(:layout)
- filter_options = options.extract!(:if, :unless).merge(:only => actions)
- cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options)
-
- around_filter ActionCacheFilter.new(cache_options), filter_options
- end
- end
-
- def _save_fragment(name, options)
- content = ""
- response_body.each do |parts|
- content << parts
- end
-
- if caching_allowed?
- write_fragment(name, content, options)
- else
- content
- end
- end
-
- protected
- def expire_action(options = {})
- return unless cache_configured?
-
- if options.is_a?(Hash) && options[:action].is_a?(Array)
- options[:action].each {|action| expire_action(options.merge(:action => action)) }
- else
- expire_fragment(ActionCachePath.new(self, options, false).path)
- end
- end
-
- class ActionCacheFilter #:nodoc:
- def initialize(options, &block)
- @cache_path, @store_options, @cache_layout =
- options.values_at(:cache_path, :store_options, :layout)
- end
-
- def filter(controller)
- path_options = if @cache_path.respond_to?(:call)
- controller.instance_exec(controller, &@cache_path)
- else
- @cache_path
- end
-
- cache_path = ActionCachePath.new(controller, path_options || {})
-
- body = controller.read_fragment(cache_path.path, @store_options)
-
- unless body
- controller.action_has_layout = false unless @cache_layout
- yield
- controller.action_has_layout = true
- body = controller._save_fragment(cache_path.path, @store_options)
- end
-
- body = controller.render_to_string(:text => body, :layout => true) unless @cache_layout
-
- controller.response_body = body
- controller.content_type = Mime[cache_path.extension || :html]
- end
- end
-
- class ActionCachePath
- attr_reader :path, :extension
-
- # If +infer_extension+ is true, the cache path extension is looked up from the request's
- # path and format. This is desirable when reading and writing the cache, but not when
- # expiring the cache - expire_action should expire the same files regardless of the
- # request format.
- def initialize(controller, options = {}, infer_extension = true)
- if infer_extension
- @extension = controller.params[:format]
- options.reverse_merge!(:format => @extension) if options.is_a?(Hash)
- end
-
- path = controller.url_for(options).split(%r{://}).last
- @path = normalize!(path)
- end
-
- private
- def normalize!(path)
- path << 'index' if path[-1] == ?/
- path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}")
- URI.parser.unescape(path)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index abeb49d16f..879d5fdd94 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -1,59 +1,29 @@
-module ActionController #:nodoc:
+module ActionController
module Caching
- # Fragment caching is used for caching various blocks within
+ # Fragment caching is used for caching various blocks within
# views 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
+ # 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 done using
- # the <tt>cache</tt> helper available in the Action View. A
- # template with fragment caching might look like:
+ # the +cache+ helper available in the Action View. See
+ # ActionView::Helpers::CacheHelper for more information.
#
- # <b>Hello <%= @name %></b>
+ # While it's strongly recommended that you use key-based cache
+ # expiration (see links in CacheHelper for more information),
+ # it is also possible to manually expire caches. For example:
#
- # <% cache do %>
- # All the topics in the system:
- # <%= render :partial => "topic", :collection => Topic.all %>
- # <% end %>
- #
- # This cache will bind 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:
- #
- # expire_fragment(:controller => "topics", :action => "list")
- #
- # This default behavior is limited if you need to cache multiple
- # fragments per action or if the action itself is cached using
- # <tt>caches_action</tt>. To remedy this, there is an option to
- # qualify the name of the cached fragment by using the
- # <tt>:action_suffix</tt> option:
- #
- # <% cache(:action => "list", :action_suffix => "all_topics") do %>
- #
- # That would result in a name such as
- # <tt>/topics/list/all_topics</tt>, 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")
+ # expire_fragment('name_of_cache')
module Fragments
- # 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 <tt>views/</tt> and uses
+ # Given a key (as described in +expire_fragment+), returns
+ # a key suitable for use in reading, writing, or expiring a
+ # cached fragment. All keys are prefixed with <tt>views/</tt> 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
- # Writes <tt>content</tt> to the location signified by
- # <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats).
+ # Writes +content+ to the location signified by
+ # +key+ (see +expire_fragment+ for acceptable formats).
def write_fragment(key, content, options = nil)
return content unless cache_configured?
@@ -65,8 +35,8 @@ module ActionController #:nodoc:
content
end
- # Reads a cached fragment from the location signified by <tt>key</tt>
- # (see <tt>expire_fragment</tt> for acceptable formats).
+ # Reads a cached fragment from the location signified by +key+
+ # (see +expire_fragment+ for acceptable formats).
def read_fragment(key, options = nil)
return unless cache_configured?
@@ -77,8 +47,8 @@ module ActionController #:nodoc:
end
end
- # Check if a cached fragment from the location signified by
- # <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats)
+ # Check if a cached fragment from the location signified by
+ # +key+ exists (see +expire_fragment+ for acceptable formats).
def fragment_exist?(key, options = nil)
return unless cache_configured?
key = fragment_cache_key(key)
@@ -95,7 +65,7 @@ module ActionController #:nodoc:
# * String - This would normally take the form of a path, like
# <tt>pages/45/notes</tt>.
# * Hash - Treated as an implicit call to +url_for+, like
- # <tt>{:controller => "pages", :action => "notes", :id => 45}</tt>
+ # <tt>{ controller: 'pages', action: 'notes', id: 45}</tt>
# * Regexp - Will remove any fragment that matches, so
# <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
# don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
@@ -104,8 +74,8 @@ module ActionController #:nodoc:
# only supported on caches that can iterate over all keys (unlike
# memcached).
#
- # +options+ is passed through to the cache store's <tt>delete</tt>
- # method (or <tt>delete_matched</tt>, for Regexp keys.)
+ # +options+ is passed through to the cache store's +delete+
+ # method (or <tt>delete_matched</tt>, for Regexp keys).
def expire_fragment(key, options = nil)
return unless cache_configured?
key = fragment_cache_key(key) unless key.is_a?(Regexp)
@@ -119,7 +89,7 @@ module ActionController #:nodoc:
end
end
- def instrument_fragment_cache(name, key)
+ def instrument_fragment_cache(name, key) # :nodoc:
ActiveSupport::Notifications.instrument("#{name}.action_controller", :key => key){ yield }
end
end
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
deleted file mode 100644
index 159f718029..0000000000
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-require 'fileutils'
-require 'active_support/core_ext/class/attribute_accessors'
-
-module ActionController #:nodoc:
- module Caching
- # 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 Action Pack. This is the fastest way to cache your content as opposed to going dynamically
- # through the process of 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.
- #
- # Specifying which actions to cache is done through the <tt>caches_page</tt> class method:
- #
- # class WeblogController < ActionController::Base
- # caches_page :show, :new
- # end
- #
- # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used
- # that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the
- # existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack.
- # This is much faster than handling the full dynamic request in the usual way.
- #
- # 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
- #
- # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
- # expired.
- module Pages
- extend ActiveSupport::Concern
-
- included do
- # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
- # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>Rails.root + "/public"</tt>). Changing
- # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
- # web server to look in the new location for cached files.
- class_attribute :page_cache_directory
- self.page_cache_directory ||= ''
-
- # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
- # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
- # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
- # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
- class_attribute :page_cache_extension
- self.page_cache_extension ||= '.html'
-
- # The compression used for gzip. If false (default), the page is not compressed.
- # If can be a symbol showing the ZLib compression method, for example, :best_compression
- # or :best_speed or an integer configuring the compression level.
- class_attribute :page_cache_compression
- self.page_cache_compression ||= false
- 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
- path = page_cache_path(path)
-
- instrument_page_cache :expire_page, path do
- File.delete(path) if File.exist?(path)
- File.delete(path + '.gz') if File.exist?(path + '.gz')
- end
- 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, extension = nil, gzip = Zlib::BEST_COMPRESSION)
- return unless perform_caching
- path = page_cache_path(path, extension)
-
- instrument_page_cache :write_page, path do
- FileUtils.makedirs(File.dirname(path))
- File.open(path, "wb+") { |f| f.write(content) }
- if gzip
- Zlib::GzipWriter.open(path + '.gz', gzip) { |f| f.write(content) }
- end
- end
- 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.
- #
- # You can also pass a :gzip option to override the class configuration one.
- #
- # Usage:
- #
- # # cache the index action
- # caches_page :index
- #
- # # cache the index action except for JSON requests
- # caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
- #
- # # don't gzip images
- # caches_page :image, :gzip => false
- def caches_page(*actions)
- return unless perform_caching
- options = actions.extract_options!
-
- gzip_level = options.fetch(:gzip, page_cache_compression)
- gzip_level = case gzip_level
- when Symbol
- Zlib.const_get(gzip_level.to_s.upcase)
- when Fixnum
- gzip_level
- when false
- nil
- else
- Zlib::BEST_COMPRESSION
- end
-
- after_filter({:only => actions}.merge(options)) do |c|
- c.cache_page(nil, nil, gzip_level)
- end
- end
-
- private
- def page_cache_file(path, extension)
- name = (path.empty? || path == "/") ? "/index" : URI.parser.unescape(path.chomp('/'))
- unless (name.split('/').last || name).include? '.'
- name << (extension || self.page_cache_extension)
- end
- return name
- end
-
- def page_cache_path(path, extension = nil)
- page_cache_directory.to_s + page_cache_file(path, extension)
- end
-
- def instrument_page_cache(name, path)
- ActiveSupport::Notifications.instrument("#{name}.action_controller", :path => path){ yield }
- 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 self.class.perform_caching
-
- if options.is_a?(Hash)
- if options[:action].is_a?(Array)
- options[:action].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
- else
- self.class.expire_page(options)
- end
- 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 url of the current request being handled is used. Example:
- # cache_page "I'm the cached content", :controller => "lists", :action => "show"
- def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
- return unless self.class.perform_caching && caching_allowed?
-
- path = case options
- when Hash
- url_for(options.merge(:only_path => true, :format => params[:format]))
- when String
- options
- else
- request.path
- end
-
- if (type = Mime::LOOKUP[self.content_type]) && (type_symbol = type.symbol).present?
- extension = ".#{type_symbol}"
- end
-
- self.class.cache_page(content || response.body, path, extension, gzip)
- end
-
- end
- end
-end
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
deleted file mode 100644
index 49cf70ec21..0000000000
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ /dev/null
@@ -1,97 +0,0 @@
-module ActionController #:nodoc:
- module Caching
- # 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 < ActionController::Caching::Sweeper
- # observe List, Item
- #
- # def after_save(record)
- # 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
- #
- # The sweeper is assigned in 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 for expiring those caches.
- #
- # You can also name an explicit class in the declaration of a sweeper, which is needed if the sweeper is in a module:
- #
- # class ListsController < ApplicationController
- # caches_action :index, :show, :public, :feed
- # cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ]
- # end
- module Sweeping
- extend ActiveSupport::Concern
-
- module ClassMethods #:nodoc:
- def cache_sweeper(*sweepers)
- configuration = sweepers.extract_options!
-
- sweepers.each do |sweeper|
- ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
- sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.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) and defined?(ActiveRecord::Observer)
- class Sweeper < ActiveRecord::Observer #:nodoc:
- attr_accessor :controller
-
- def before(controller)
- self.controller = controller
- callback(:before) if controller.perform_caching
- true # before method from sweeper should always return true
- end
-
- def after(controller)
- self.controller = controller
- callback(:after) if controller.perform_caching
- # Clean up, so that the controller can be collected after this request
- self.controller = nil
- end
-
- protected
- # gets the action cache path for the given options.
- def action_path_for(options)
- Actions::ActionCachePath.new(controller, options).path
- end
-
- # Retrieve instance variables set in the controller.
- def assigns(key)
- controller.instance_variable_get("@#{key}")
- 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, true)
- __send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
- end
-
- def method_missing(method, *arguments, &block)
- return unless @controller
- @controller.__send__(method, *arguments, &block)
- end
- end
- end
- end
-end