Merge pull request #7833 from frodsan/extract_ap_pages_actions_caching

Extract AP Page and Action caching from Rails
This commit is contained in:
Rafael Mendonça França 2012-10-04 10:39:06 -07:00
commit b0a7068564
8 changed files with 65 additions and 1138 deletions

View File

@ -1,5 +1,16 @@
## Rails 4.0.0 (unreleased) ##
* `ActionController::Base.page_cache_extension` option is deprecated
in favour of `ActionController::Base.default_static_extension`.
*Francesco Rodriguez*
* Action and Page caching has been extracted from Action Dispatch
as `actionpack-action_caching` and `actionpack-page_caching` gems.
Please read the `README.md` file on both gems for the usage.
*Francesco Rodriguez*
* Failsafe exception returns text/plain. *Steve Klabnik*
* Remove actionpack's rack-cache dependency and declare the

View File

@ -2,11 +2,9 @@ require 'fileutils'
require 'uri'
require 'set'
module ActionController #:nodoc:
module ActionController
# \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.
@ -17,8 +15,7 @@ module ActionController #:nodoc:
# == \Caching stores
#
# All the caching stores from ActiveSupport::Cache are available to be used as backends
# for Action Controller caching. This setting only affects action and fragment caching
# as page caching is always written to disk.
# for Action Controller caching.
#
# Configuration examples (MemoryStore is the default):
#
@ -32,9 +29,7 @@ module ActionController #:nodoc:
extend ActiveSupport::Autoload
eager_autoload do
autoload :Actions
autoload :Fragments
autoload :Pages
autoload :Sweeper, 'action_controller/caching/sweeping'
autoload :Sweeping, 'action_controller/caching/sweeping'
end
@ -58,12 +53,25 @@ module ActionController #:nodoc:
include AbstractController::Callbacks
include ConfigMethods
include Pages, Actions, Fragments
include Fragments
include Sweeping if defined?(ActiveRecord)
included do
extend ConfigMethods
config_accessor :default_static_extension
self.default_static_extension ||= '.html'
def self.page_cache_extension=(extension)
ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
self.default_static_extension = extension
end
def self.page_cache_extension
ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
default_static_extension
end
config_accessor :perform_caching
self.perform_caching = true if perform_caching.nil?
end

View File

@ -1,189 +0,0 @@
require 'set'
module ActionController
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.
#
# 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.new</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 around(controller)
cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
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('://', 2).last
@path = normalize!(path)
end
private
def normalize!(path)
ext = URI.parser.escape(extension) if extension
path << 'index' if path[-1] == ?/
path << ".#{ext}" if extension and !path.split('?', 2).first.ends_with?(".#{ext}")
URI.parser.unescape(path)
end
end
end
end
end

View File

@ -1,202 +0,0 @@
require 'fileutils'
require 'active_support/core_ext/class/attribute_accessors'
module ActionController
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 +caches_page+ 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, <tt>:best_compression</tt>
# or <tt>:best_speed</tt> 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.
#
# 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+.
#
# 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 <tt>:gzip</tt> option to override the class configuration one.
#
# # cache the index action
# caches_page :index
#
# # cache the index action except for JSON requests
# caches_page :index, if: Proc.new { !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.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.
#
# 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.
#
# 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

View File

@ -29,7 +29,7 @@ module ActionDispatch
def ext
@ext ||= begin
ext = ::ActionController::Base.page_cache_extension
ext = ::ActionController::Base.default_static_extension
"{,#{ext},/index#{ext}}"
end
end

View File

@ -6,744 +6,40 @@ CACHE_DIR = 'test_cache'
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
class CachingMetalController < ActionController::Metal
class FragmentCachingMetalTestController < ActionController::Metal
abstract!
include ActionController::Caching
self.page_cache_directory = FILE_STORE_PATH
self.cache_store = :file_store, FILE_STORE_PATH
def some_action; end
end
class PageCachingMetalTestController < CachingMetalController
caches_page :ok
def ok
self.response_body = 'ok'
end
end
class PageCachingMetalTest < ActionController::TestCase
tests PageCachingMetalTestController
class FragmentCachingMetalTest < ActionController::TestCase
def setup
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
FileUtils.mkdir_p(FILE_STORE_PATH)
super
@store = ActiveSupport::Cache::MemoryStore.new
@controller = FragmentCachingMetalTestController.new
@controller.perform_caching = true
@controller.cache_store = @store
@params = { controller: 'posts', action: 'index'}
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@controller.params = @params
@controller.request = @request
@controller.response = @response
end
def teardown
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
end
def test_should_cache_get_with_ok_status
get :ok
assert_response :ok
assert File.exist?("#{FILE_STORE_PATH}/page_caching_metal_test/ok.html"), 'get with ok status should have been cached'
def test_fragment_cache_key
assert_equal 'views/what a key', @controller.fragment_cache_key('what a key')
end
end
ActionController::Base.page_cache_directory = FILE_STORE_PATH
class CachingController < ActionController::Base
abstract!
self.cache_store = :file_store, FILE_STORE_PATH
end
class PageCachingTestController < CachingController
self.page_cache_compression = :best_compression
caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
caches_page :found, :not_found
caches_page :about_me
caches_page :default_gzip
caches_page :no_gzip, :gzip => false
caches_page :gzip_level, :gzip => :best_speed
def ok
head :ok
end
def no_content
head :no_content
end
def found
redirect_to :action => 'ok'
end
def not_found
head :not_found
end
def custom_path
render :text => "Super soaker"
cache_page("Super soaker", "/index.html")
end
def default_gzip
render :text => "Text"
end
def no_gzip
render :text => "PNG"
end
def gzip_level
render :text => "Big text"
end
def expire_custom_path
expire_page("/index.html")
head :ok
end
def trailing_slash
render :text => "Sneak attack"
end
def about_me
respond_to do |format|
format.html {render :text => 'I am html'}
format.xml {render :text => 'I am xml'}
end
end
end
class PageCachingTest < ActionController::TestCase
def setup
super
@request = ActionController::TestRequest.new
@request.host = 'hostname.com'
@request.env.delete('PATH_INFO')
@controller = PageCachingTestController.new
@controller.perform_caching = true
@controller.cache_store = :file_store, FILE_STORE_PATH
@response = ActionController::TestResponse.new
@params = {:controller => 'posts', :action => 'index', :only_path => true}
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
FileUtils.mkdir_p(FILE_STORE_PATH)
end
def teardown
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
@controller.perform_caching = false
end
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
with_routing do |set|
set.draw do
get 'posts.:format', :to => 'posts#index', :as => :formatted_posts
get '/', :to => 'posts#index', :as => :main
end
@params[:format] = 'rss'
assert_equal '/posts.rss', @routes.url_for(@params)
@params[:format] = nil
assert_equal '/', @routes.url_for(@params)
end
end
def test_should_cache_get_with_ok_status
get :ok
assert_response :ok
assert_page_cached :ok, "get with ok status should have been cached"
end
def test_should_cache_with_custom_path
get :custom_path
assert File.exist?("#{FILE_STORE_PATH}/index.html")
end
def test_should_expire_cache_with_custom_path
get :custom_path
assert File.exist?("#{FILE_STORE_PATH}/index.html")
get :expire_custom_path
assert !File.exist?("#{FILE_STORE_PATH}/index.html")
end
def test_should_gzip_cache
get :custom_path
assert File.exist?("#{FILE_STORE_PATH}/index.html.gz")
get :expire_custom_path
assert !File.exist?("#{FILE_STORE_PATH}/index.html.gz")
end
def test_should_allow_to_disable_gzip
get :no_gzip
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html")
assert !File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html.gz")
end
def test_should_use_config_gzip_by_default
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION)
get :default_gzip
end
def test_should_set_gzip_level
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED)
get :gzip_level
end
def test_should_cache_without_trailing_slash_on_url
@controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash'
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
end
def test_should_obey_http_accept_attribute
@request.env['HTTP_ACCEPT'] = 'text/xml'
get :about_me
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/about_me.xml")
assert_equal 'I am xml', @response.body
end
def test_cached_page_should_not_have_trailing_slash_even_if_url_has_trailing_slash
@controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/'
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
end
def test_should_cache_ok_at_custom_path
@request.env['PATH_INFO'] = '/index.html'
get :ok
assert_response :ok
assert File.exist?("#{FILE_STORE_PATH}/index.html")
end
[:ok, :no_content, :found, :not_found].each do |status|
[:get, :post, :patch, :put, :delete].each do |method|
unless method == :get && status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status)
assert_response status
assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached"
end
end
end
end
def test_page_caching_conditional_options
get :ok, :format=>'json'
assert_page_not_cached :ok
end
def test_page_caching_directory_set_as_pathname
begin
ActionController::Base.page_cache_directory = Pathname.new(FILE_STORE_PATH)
get :ok
assert_response :ok
assert_page_cached :ok
ensure
ActionController::Base.page_cache_directory = FILE_STORE_PATH
end
end
private
def assert_page_cached(action, message = "#{action} should have been cached")
assert page_cached?(action), message
end
def assert_page_not_cached(action, message = "#{action} shouldn't have been cached")
assert !page_cached?(action), message
end
def page_cached?(action)
File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html"
end
end
class ActionCachingTestController < CachingController
rescue_from(Exception) { head 500 }
rescue_from(ActionController::UnknownFormat) { head :not_acceptable }
if defined? ActiveRecord
rescue_from(ActiveRecord::RecordNotFound) { head :not_found }
end
# Eliminate uninitialized ivar warning
before_filter { @title = nil }
caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| c.request.format && !c.request.format.json? }, :expires_in => 1.hour
caches_action :show, :cache_path => 'http://test.host/custom/show'
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
caches_action :with_layout
caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } }
caches_action :layout_false, :layout => false
caches_action :with_layout_proc_param, :layout => Proc.new { |c| c.params[:layout] }
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
caches_action :streaming
caches_action :invalid
layout 'talk_from_action'
def index
@cache_this = MockTime.now.to_f.to_s
render :text => @cache_this
end
def redirected
redirect_to :action => 'index'
end
def forbidden
render :text => "Forbidden"
response.status = "403 Forbidden"
end
def with_layout
@cache_this = MockTime.now.to_f.to_s
@title = nil
render :text => @cache_this, :layout => true
end
def with_format_and_http_param
@cache_this = MockTime.now.to_f.to_s
render :text => @cache_this
end
def record_not_found
raise ActiveRecord::RecordNotFound, "oops!"
end
def four_oh_four
render :text => "404'd!", :status => 404
end
def simple_runtime_error
raise "oops!"
end
alias_method :show, :index
alias_method :edit, :index
alias_method :destroy, :index
alias_method :layout_false, :with_layout
alias_method :with_layout_proc_param, :with_layout
def expire
expire_action :controller => 'action_caching_test', :action => 'index'
render :nothing => true
end
def expire_xml
expire_action :controller => 'action_caching_test', :action => 'index', :format => 'xml'
render :nothing => true
end
def expire_with_url_string
expire_action url_for(:controller => 'action_caching_test', :action => 'index')
render :nothing => true
end
def streaming
render :text => "streaming", :stream => true
end
def invalid
@cache_this = MockTime.now.to_f.to_s
respond_to do |format|
format.json{ render :json => @cache_this }
end
end
end
class MockTime < Time
# Let Time spicy to assure that Time.now != Time.now
def to_f
super+rand
end
end
class ActionCachingMockController
attr_accessor :mock_url_for
attr_accessor :mock_path
def initialize
yield self if block_given?
end
def url_for(*args)
@mock_url_for
end
def params
request.parameters
end
def request
Object.new.instance_eval(<<-EVAL)
def path; '#{@mock_path}' end
def format; 'all' end
def parameters; {:format => nil}; end
self
EVAL
end
end
class ActionCacheTest < ActionController::TestCase
tests ActionCachingTestController
def setup
super
@request.host = 'hostname.com'
FileUtils.mkdir_p(FILE_STORE_PATH)
@path_class = ActionController::Caching::Actions::ActionCachePath
@mock_controller = ActionCachingMockController.new
end
def teardown
super
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
end
def test_simple_action_cache
get :index
assert_response :success
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test')
get :index
assert_response :success
assert_equal cached_time, @response.body
end
def test_simple_action_not_cached
get :destroy
assert_response :success
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert !fragment_exist?('hostname.com/action_caching_test/destroy')
get :destroy
assert_response :success
assert_not_equal cached_time, @response.body
end
include RackTestUtils
def test_action_cache_with_layout
get :with_layout
assert_response :success
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/with_layout')
get :with_layout
assert_response :success
assert_not_equal cached_time, @response.body
body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout'))
assert_equal @response.body, body
end
def test_action_cache_with_layout_and_layout_cache_false
get :layout_false
assert_response :success
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/layout_false')
get :layout_false
assert_response :success
assert_not_equal cached_time, @response.body
body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false'))
assert_equal cached_time, body
end
def test_action_cache_with_layout_and_layout_cache_false_via_proc
get :with_layout_proc_param, :layout => false
assert_response :success
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
get :with_layout_proc_param, :layout => false
assert_response :success
assert_not_equal cached_time, @response.body
body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
assert_equal cached_time, body
end
def test_action_cache_with_layout_and_layout_cache_true_via_proc
get :with_layout_proc_param, :layout => true
assert_response :success
cached_time = content_to_cache
assert_not_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
get :with_layout_proc_param, :layout => true
assert_response :success
assert_not_equal cached_time, @response.body
body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
assert_equal @response.body, body
end
def test_action_cache_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json'
get :index
assert_response :success
assert !fragment_exist?('hostname.com/action_caching_test')
end
def test_action_cache_with_format_and_http_param
get :with_format_and_http_param, :format => 'json'
assert_response :success
assert !fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value.json')
assert fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value')
end
def test_action_cache_with_store_options
MockTime.expects(:now).returns(12345).once
@controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once
@controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once
get :index
assert_response :success
end
def test_action_cache_with_custom_cache_path
get :show
assert_response :success
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert fragment_exist?('test.host/custom/show')
get :show
assert_response :success
assert_equal cached_time, @response.body
end
def test_action_cache_with_custom_cache_path_in_block
get :edit
assert_response :success
assert fragment_exist?('test.host/edit')
get :edit, :id => 1
assert_response :success
assert fragment_exist?('test.host/1;edit')
end
def test_cache_expiration
get :index
assert_response :success
cached_time = content_to_cache
get :index
assert_response :success
assert_equal cached_time, @response.body
get :expire
assert_response :success
get :index
assert_response :success
new_cached_time = content_to_cache
assert_not_equal cached_time, @response.body
get :index
assert_response :success
assert_equal new_cached_time, @response.body
end
def test_cache_expiration_isnt_affected_by_request_format
get :index
cached_time = content_to_cache
@request.request_uri = "/action_caching_test/expire.xml"
get :expire, :format => :xml
assert_response :success
get :index
assert_response :success
assert_not_equal cached_time, @response.body
end
def test_cache_expiration_with_url_string
get :index
cached_time = content_to_cache
@request.request_uri = "/action_caching_test/expire_with_url_string"
get :expire_with_url_string
assert_response :success
get :index
assert_response :success
assert_not_equal cached_time, @response.body
end
def test_cache_is_scoped_by_subdomain
@request.host = 'jamis.hostname.com'
get :index
assert_response :success
jamis_cache = content_to_cache
@request.host = 'david.hostname.com'
get :index
assert_response :success
david_cache = content_to_cache
assert_not_equal jamis_cache, @response.body
@request.host = 'jamis.hostname.com'
get :index
assert_response :success
assert_equal jamis_cache, @response.body
@request.host = 'david.hostname.com'
get :index
assert_response :success
assert_equal david_cache, @response.body
end
def test_redirect_is_not_cached
get :redirected
assert_response :redirect
get :redirected
assert_response :redirect
end
def test_forbidden_is_not_cached
get :forbidden
assert_response :forbidden
get :forbidden
assert_response :forbidden
end
def test_xml_version_of_resource_is_treated_as_different_cache
with_routing do |set|
set.draw do
get ':controller(/:action(.:format))'
end
get :index, :format => 'xml'
assert_response :success
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert fragment_exist?('hostname.com/action_caching_test/index.xml')
get :index, :format => 'xml'
assert_response :success
assert_equal cached_time, @response.body
assert_equal 'application/xml', @response.content_type
get :expire_xml
assert_response :success
get :index, :format => 'xml'
assert_response :success
assert_not_equal cached_time, @response.body
end
end
def test_correct_content_type_is_returned_for_cache_hit
# run it twice to cache it the first time
get :index, :id => 'content-type', :format => 'xml'
get :index, :id => 'content-type', :format => 'xml'
assert_response :success
assert_equal 'application/xml', @response.content_type
end
def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key
# run it twice to cache it the first time
get :show, :format => 'xml'
get :show, :format => 'xml'
assert_response :success
assert_equal 'application/xml', @response.content_type
end
def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key_from_proc
# run it twice to cache it the first time
get :edit, :id => 1, :format => 'xml'
get :edit, :id => 1, :format => 'xml'
assert_response :success
assert_equal 'application/xml', @response.content_type
end
def test_empty_path_is_normalized
@mock_controller.mock_url_for = 'http://example.org/'
@mock_controller.mock_path = '/'
assert_equal 'example.org/index', @path_class.new(@mock_controller, {}).path
end
def test_file_extensions
get :index, :id => 'kitten.jpg'
get :index, :id => 'kitten.jpg'
assert_response :success
end
if defined? ActiveRecord
def test_record_not_found_returns_404_for_multiple_requests
get :record_not_found
assert_response 404
get :record_not_found
assert_response 404
end
end
def test_four_oh_four_returns_404_for_multiple_requests
get :four_oh_four
assert_response 404
get :four_oh_four
assert_response 404
end
def test_four_oh_four_renders_content
get :four_oh_four
assert_equal "404'd!", @response.body
end
def test_simple_runtime_error_returns_500_for_multiple_requests
get :simple_runtime_error
assert_response 500
get :simple_runtime_error
assert_response 500
end
def test_action_caching_plus_streaming
get :streaming
assert_response :success
assert_match(/streaming/, @response.body)
assert fragment_exist?('hostname.com/action_caching_test/streaming')
end
def test_invalid_format_returns_not_acceptable
get :invalid, :format => "json"
assert_response :success
cached_time = content_to_cache
assert_equal cached_time, @response.body
assert fragment_exist?("hostname.com/action_caching_test/invalid.json")
get :invalid, :format => "json"
assert_response :success
assert_equal cached_time, @response.body
get :invalid, :format => "xml"
assert_response :not_acceptable
get :invalid, :format => "\xC3\x83"
assert_response :not_acceptable
end
private
def content_to_cache
assigns(:cache_this)
end
def fragment_exist?(path)
@controller.fragment_exist?(path)
end
def read_fragment(path)
@controller.read_fragment(path)
end
end
class FragmentCachingTestController < CachingController
def some_action; end;
end
@ -988,3 +284,17 @@ class CacheHelperOutputBufferTest < ActionController::TestCase
end
end
class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase
def test_page_cache_extension_binds_default_static_extension
deprecation_behavior = ActiveSupport::Deprecation.behavior
ActiveSupport::Deprecation.behavior = :silence
old_extension = ActionController::Base.default_static_extension
ActionController::Base.page_cache_extension = '.rss'
assert_equal '.rss', ActionController::Base.default_static_extension
ensure
ActiveSupport::Deprecation.behavior = deprecation_behavior
ActionController::Base.default_static_extension = old_extension
end
end

View File

@ -42,11 +42,6 @@ module Another
render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>"
end
def with_page_cache
cache_page("Super soaker", "/index.html")
render :nothing => true
end
def with_exception
raise Exception
end
@ -71,7 +66,6 @@ class ACLogSubscriberTest < ActionController::TestCase
@old_logger = ActionController::Base.logger
@cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__))
ActionController::Base.page_cache_directory = @cache_path
@controller.cache_store = :file_store, @cache_path
ActionController::LogSubscriber.attach_to :action_controller
end
@ -199,18 +193,6 @@ class ACLogSubscriberTest < ActionController::TestCase
@controller.config.perform_caching = true
end
def test_with_page_cache
@controller.config.perform_caching = true
get :with_page_cache
wait
assert_equal 3, logs.size
assert_match(/Write page/, logs[1])
assert_match(/\/index\.html/, logs[1])
ensure
@controller.config.perform_caching = true
end
def test_process_action_with_exception_includes_http_status_code
begin
get :with_exception

View File

@ -69,6 +69,13 @@ in the `config/initializers/wrap_parameters.rb` file:
### Action Pack
Rails 4.0 has deprecated `ActionController::Base.page_cache_extension` option. Use
`ActionController::Base.default_static_extension` instead.
Rails 4.0 has removed Action and Page caching from ActionPack. You will need to
add the `actionpack-action_caching` gem in order to use `caches_action` and
the `actionpack-page_caching` to use `caches_pages` in your controllers.
Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`.
Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example: