actionpack-page_caching-1.2.4/ 0000755 0000041 0000041 00000000000 14054357527 016264 5 ustar www-data www-data actionpack-page_caching-1.2.4/test/ 0000755 0000041 0000041 00000000000 14054357527 017243 5 ustar www-data www-data actionpack-page_caching-1.2.4/test/abstract_unit.rb 0000644 0000041 0000041 00000000526 14054357527 022435 0 ustar www-data www-data require "bundler/setup"
require "minitest/autorun"
require "action_controller"
require "action_controller/page_caching"
if ActiveSupport.respond_to?(:test_order)
ActiveSupport.test_order = :random
end
if ActionController::Base.respond_to?(:enable_fragment_cache_logging=)
ActionController::Base.enable_fragment_cache_logging = true
end
actionpack-page_caching-1.2.4/test/caching_test.rb 0000644 0000041 0000041 00000033450 14054357527 022230 0 ustar www-data www-data require "rails/version"
require "abstract_unit"
require "mocha/minitest"
require "find"
CACHE_DIR = "test_cache"
# Don't change "../tmp" cavalierly or you might hose something you don't want hosed
TEST_TMP_DIR = File.expand_path("../tmp", __FILE__)
FILE_STORE_PATH = File.join(TEST_TMP_DIR, CACHE_DIR)
module PageCachingTestHelpers
def setup
super
@routes = ActionDispatch::Routing::RouteSet.new
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
FileUtils.mkdir_p(FILE_STORE_PATH)
end
def teardown
super
FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
@controller.perform_caching = false
end
private
def assert_page_cached(action, options = {})
expected = options[:content] || action.to_s
path = cache_file(action, options)
assert File.exist?(path), "The cache file #{path} doesn't exist"
if File.extname(path) == ".gz"
actual = Zlib::GzipReader.open(path) { |f| f.read }
else
actual = File.read(path)
end
assert_equal expected, actual, "The cached content doesn't match the expected value"
end
def assert_page_not_cached(action, options = {})
path = cache_file(action, options)
assert !File.exist?(path), "The cache file #{path} still exists"
end
def cache_file(action, options = {})
path = options[:path] || FILE_STORE_PATH
controller = options[:controller] || self.class.name.underscore
format = options[:format] || "html"
"#{path}/#{controller}/#{action}.#{format}"
end
def draw(&block)
@routes = ActionDispatch::Routing::RouteSet.new
@routes.draw(&block)
@controller.extend(@routes.url_helpers)
end
end
class CachingMetalController < ActionController::Metal
abstract!
include AbstractController::Callbacks
include ActionController::Caching
self.page_cache_directory = FILE_STORE_PATH
self.cache_store = :file_store, FILE_STORE_PATH
end
class PageCachingMetalTestController < CachingMetalController
caches_page :ok
def ok
self.response_body = "ok"
end
end
class PageCachingMetalTest < ActionController::TestCase
include PageCachingTestHelpers
tests PageCachingMetalTestController
def test_should_cache_get_with_ok_status
draw do
get "/page_caching_metal_test/ok", to: "page_caching_metal_test#ok"
end
get :ok
assert_response :ok
assert_page_cached :ok
end
end
ActionController::Base.page_cache_directory = FILE_STORE_PATH
class CachingController < ActionController::Base
abstract!
self.cache_store = :file_store, FILE_STORE_PATH
protected
if ActionPack::VERSION::STRING < "4.1"
def render(options)
if options.key?(:html)
super({ text: options.delete(:html) }.merge(options))
else
super
end
end
end
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
render html: "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 html: "custom_path"
cache_page(nil, "/index.html")
end
def default_gzip
render html: "default_gzip"
end
def no_gzip
render html: "no_gzip"
end
def gzip_level
render html: "gzip_level"
end
def expire_custom_path
expire_page("/index.html")
head :ok
end
def trailing_slash
render html: "trailing_slash"
end
def about_me
respond_to do |format|
format.html { render html: "I am html" }
format.xml { render xml: "I am xml" }
end
end
end
class PageCachingTest < ActionController::TestCase
include PageCachingTestHelpers
tests PageCachingTestController
def test_cache_does_not_escape
draw do
get "/page_caching_test/ok/:id", to: "page_caching_test#ok"
end
project_root = File.expand_path("../../", __FILE__)
# Make a path that escapes the cache directory
get_to_root = "../../../"
# Make sure this relative path points at the project root
assert_equal project_root, File.expand_path(File.join(FILE_STORE_PATH, get_to_root))
if Rails.version =~ /^4\./
get :ok, id: "#{get_to_root}../pwnd"
else
get :ok, params: { id: "#{get_to_root}../pwnd" }
end
assert_predicate Find.find(File.join(project_root, "test")).grep(/pwnd/), :empty?
end
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
draw do
get "posts.:format", to: "posts#index", as: :formatted_posts
get "/", to: "posts#index", as: :main
end
defaults = { controller: "posts", action: "index", only_path: true }
assert_equal "/posts.rss", @routes.url_for(defaults.merge(format: "rss"))
assert_equal "/", @routes.url_for(defaults.merge(format: nil))
end
def test_should_cache_head_with_ok_status
draw do
get "/page_caching_test/ok", to: "page_caching_test#ok"
end
head :ok
assert_response :ok
assert_page_cached :ok
end
def test_should_cache_get_with_ok_status
draw do
get "/page_caching_test/ok", to: "page_caching_test#ok"
end
get :ok
assert_response :ok
assert_page_cached :ok
end
def test_should_cache_with_custom_path
draw do
get "/page_caching_test/custom_path", to: "page_caching_test#custom_path"
end
get :custom_path
assert_page_cached :index, controller: ".", content: "custom_path"
end
def test_should_expire_cache_with_custom_path
draw do
get "/page_caching_test/custom_path", to: "page_caching_test#custom_path"
get "/page_caching_test/expire_custom_path", to: "page_caching_test#expire_custom_path"
end
get :custom_path
assert_page_cached :index, controller: ".", content: "custom_path"
get :expire_custom_path
assert_page_not_cached :index, controller: ".", content: "custom_path"
end
def test_should_gzip_cache
draw do
get "/page_caching_test/custom_path", to: "page_caching_test#custom_path"
get "/page_caching_test/expire_custom_path", to: "page_caching_test#expire_custom_path"
end
get :custom_path
assert_page_cached :index, controller: ".", format: "html.gz", content: "custom_path"
get :expire_custom_path
assert_page_not_cached :index, controller: ".", format: "html.gz"
end
def test_should_allow_to_disable_gzip
draw do
get "/page_caching_test/no_gzip", to: "page_caching_test#no_gzip"
end
get :no_gzip
assert_page_cached :no_gzip, format: "html"
assert_page_not_cached :no_gzip, format: "html.gz"
end
def test_should_use_config_gzip_by_default
draw do
get "/page_caching_test/default_gzip", to: "page_caching_test#default_gzip"
end
@controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION)
get :default_gzip
end
def test_should_set_gzip_level
draw do
get "/page_caching_test/gzip_level", to: "page_caching_test#gzip_level"
end
@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_page_cached :trailing_slash, content: "cached content"
end
def test_should_obey_http_accept_attribute
draw do
get "/page_caching_test/about_me", to: "page_caching_test#about_me"
end
@request.env["HTTP_ACCEPT"] = "text/xml"
get :about_me
assert_equal "I am xml", @response.body
assert_page_cached :about_me, format: "xml", content: "I am xml"
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_page_cached :trailing_slash, content: "cached content"
end
def test_should_cache_ok_at_custom_path
draw do
get "/page_caching_test/ok", to: "page_caching_test#ok"
end
@request.env["PATH_INFO"] = "/index.html"
get :ok
assert_response :ok
assert_page_cached :index, controller: ".", content: "ok"
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
draw do
get "/page_caching_test/ok", to: "page_caching_test#ok"
match "/page_caching_test/#{status}", to: "page_caching_test##{status}", via: method
end
send(method, status)
assert_response status
assert_page_not_cached status
end
end
end
end
def test_page_caching_conditional_options
draw do
get "/page_caching_test/ok", to: "page_caching_test#ok"
end
get :ok, format: "json"
assert_page_not_cached :ok
end
def test_page_caching_directory_set_as_pathname
ActionController::Base.page_cache_directory = Pathname.new(FILE_STORE_PATH)
draw do
get "/page_caching_test/ok", to: "page_caching_test#ok"
end
get :ok
assert_response :ok
assert_page_cached :ok
ensure
ActionController::Base.page_cache_directory = FILE_STORE_PATH
end
def test_page_caching_directory_set_on_controller_instance
draw do
get "/page_caching_test/ok", to: "page_caching_test#ok"
end
file_store_path = File.join(TEST_TMP_DIR, "instance_cache")
@controller.page_cache_directory = file_store_path
get :ok
assert_response :ok
assert_page_cached :ok, path: file_store_path
end
end
class ProcPageCachingTestController < CachingController
self.page_cache_directory = -> { File.join(TEST_TMP_DIR, request.domain) }
caches_page :ok
def ok
render html: "ok"
end
def expire_ok
expire_page action: :ok
head :ok
end
end
class ProcPageCachingTest < ActionController::TestCase
include PageCachingTestHelpers
tests ProcPageCachingTestController
def test_page_is_cached_by_domain
draw do
get "/proc_page_caching_test/ok", to: "proc_page_caching_test#ok"
get "/proc_page_caching_test/ok/expire", to: "proc_page_caching_test#expire_ok"
end
@request.env["HTTP_HOST"] = "www.foo.com"
get :ok
assert_response :ok
assert_page_cached :ok, path: TEST_TMP_DIR + "/foo.com"
get :expire_ok
assert_response :ok
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/foo.com"
@request.env["HTTP_HOST"] = "www.bar.com"
get :ok
assert_response :ok
assert_page_cached :ok, path: TEST_TMP_DIR + "/bar.com"
get :expire_ok
assert_response :ok
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/bar.com"
end
def test_class_level_cache_page_raise_error
assert_raises(RuntimeError, /class-level cache_page method/) do
@controller.class.cache_page "cached content", "/proc_page_caching_test/ok"
end
end
end
class SymbolPageCachingTestController < CachingController
self.page_cache_directory = :domain_cache_directory
caches_page :ok
def ok
render html: "ok"
end
def expire_ok
expire_page action: :ok
head :ok
end
protected
def domain_cache_directory
File.join(TEST_TMP_DIR, request.domain)
end
end
class SymbolPageCachingTest < ActionController::TestCase
include PageCachingTestHelpers
tests SymbolPageCachingTestController
def test_page_is_cached_by_domain
draw do
get "/symbol_page_caching_test/ok", to: "symbol_page_caching_test#ok"
get "/symbol_page_caching_test/ok/expire", to: "symbol_page_caching_test#expire_ok"
end
@request.env["HTTP_HOST"] = "www.foo.com"
get :ok
assert_response :ok
assert_page_cached :ok, path: TEST_TMP_DIR + "/foo.com"
get :expire_ok
assert_response :ok
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/foo.com"
@request.env["HTTP_HOST"] = "www.bar.com"
get :ok
assert_response :ok
assert_page_cached :ok, path: TEST_TMP_DIR + "/bar.com"
get :expire_ok
assert_response :ok
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/bar.com"
end
def test_class_level_cache_page_raise_error
assert_raises(RuntimeError, /class-level cache_page method/) do
@controller.class.cache_page "cached content", "/symbol_page_caching_test/ok"
end
end
end
class CallablePageCachingTestController < CachingController
class DomainCacheDirectory
def self.call(request)
File.join(TEST_TMP_DIR, request.domain)
end
end
self.page_cache_directory = DomainCacheDirectory
caches_page :ok
def ok
render html: "ok"
end
def expire_ok
expire_page action: :ok
head :ok
end
end
class CallablePageCachingTest < ActionController::TestCase
include PageCachingTestHelpers
tests CallablePageCachingTestController
def test_page_is_cached_by_domain
draw do
get "/callable_page_caching_test/ok", to: "callable_page_caching_test#ok"
get "/callable_page_caching_test/ok/expire", to: "callable_page_caching_test#expire_ok"
end
@request.env["HTTP_HOST"] = "www.foo.com"
get :ok
assert_response :ok
assert_page_cached :ok, path: TEST_TMP_DIR + "/foo.com"
get :expire_ok
assert_response :ok
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/foo.com"
@request.env["HTTP_HOST"] = "www.bar.com"
get :ok
assert_response :ok
assert_page_cached :ok, path: TEST_TMP_DIR + "/bar.com"
get :expire_ok
assert_response :ok
assert_page_not_cached :ok, path: TEST_TMP_DIR + "/bar.com"
end
def test_class_level_cache_page_raise_error
assert_raises(RuntimeError, /class-level cache_page method/) do
@controller.class.cache_page "cached content", "/callable_page_caching_test/ok"
end
end
end
actionpack-page_caching-1.2.4/test/log_subscriber_test.rb 0000644 0000041 0000041 00000002547 14054357527 023643 0 ustar www-data www-data require "abstract_unit"
require "active_support/log_subscriber/test_helper"
require "action_controller/log_subscriber"
module Another
class LogSubscribersController < ActionController::Base
abstract!
self.perform_caching = true
def with_page_cache
cache_page("Super soaker", "/index.html")
head :ok
end
end
end
class ACLogSubscriberTest < ActionController::TestCase
tests Another::LogSubscribersController
include ActiveSupport::LogSubscriber::TestHelper
def setup
super
@routes = ActionDispatch::Routing::RouteSet.new
@cache_path = File.expand_path("../tmp/test_cache", __FILE__)
ActionController::Base.page_cache_directory = @cache_path
@controller.cache_store = :file_store, @cache_path
ActionController::LogSubscriber.attach_to :action_controller
end
def teardown
ActiveSupport::LogSubscriber.log_subscribers.clear
FileUtils.rm_rf(@cache_path)
end
def set_logger(logger)
ActionController::Base.logger = logger
end
def test_with_page_cache
with_routing do |set|
set.draw do
get "/with_page_cache", to: "another/log_subscribers#with_page_cache"
end
get :with_page_cache
wait
logs = @logger.logged(:info)
assert_equal 3, logs.size
assert_match(/Write page/, logs[1])
assert_match(/\/index\.html/, logs[1])
end
end
end
actionpack-page_caching-1.2.4/README.md 0000644 0000041 0000041 00000014143 14054357527 017546 0 ustar www-data www-data # actionpack-page_caching
Static page caching for Action Pack (removed from core in Rails 4.0).
## Introduction
Page caching is an approach to caching in which response bodies are stored in
files that the web server can serve directly:
1. A request to endpoint _E_ arrives.
2. Its response is calculated and stored in a file _F_.
3. Next time _E_ is requested, the web server sends _F_ directly.
That applies only to GET or HEAD requests whose response code is 200, the rest
are ignored.
Unlike caching proxies or other more sophisticated setups, page caching results
in a dramatic speed up while being dead simple at the same time. Awesome
cost/benefit.
The reason for such performance boost is that cached endpoints are
short-circuited by the web server, which is very efficient at serving static
files. Requests to cached endpoints do not even reach your Rails application.
This technique, however, is only suitable for pages that do not need to go
through your Rails stack, precisely. For example, content management systems
like wikis have typically 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. As a use case you can check, [Rails
Contributors](https://contributors.rubyonrails.org/) makes heavy use of page
caching. Its source code is [here](https://github.com/rails/rails-contributors).
It is not all or nothing, though, in HTML cached pages JavaScript can still
tweak details here and there dynamically as a trade-off.
## Installation
Add this line to your application's `Gemfile`:
``` ruby
gem "actionpack-page_caching"
```
And then execute:
```
$ bundle
```
## Usage
### Enable Caching
Page caching needs caching enabled:
```ruby
config.action_controller.perform_caching = true
```
That goes typically in `config/environments/production.rb`, but you can activate
that flag in any mode.
Since Rails 5 there is a special toggler to easily enable/disable caching in
`development` mode without editing its configuration file. Just execute
```
$ bin/rails dev:cache
```
to enable/disable caching in `development` mode.
### Configure the Cache Directory
#### Default Cache Directory
By default, files are stored below the `public` directory of your Rails
application, with a path that matches the one in the URL.
For example, a page-cached request to `/posts/what-is-new-in-rails-6` would be
stored by default in the file `public/posts/what-is-new-in-rails-6.html`, and
the web server would be configured to check that path in the file system before
falling back to Rails. More on this later.
#### Custom Cache Directory
The default page caching directory can be overridden:
``` ruby
config.action_controller.page_cache_directory = Rails.root.join("public", "cached_pages")
```
There is no need to ensure the directory exists when the application boots,
whenever a page has to be cached, the page cache directory is created if needed.
#### Custom Cache Directory per Controller
The globally configured cache directory, default or custom, can be overridden in
each controller. There are three ways of doing this.
With a lambda:
``` ruby
class WeblogController < ApplicationController
self.page_cache_directory = -> {
Rails.root.join("public", request.domain)
}
end
```
a symbol:
``` ruby
class WeblogController < ApplicationController
self.page_cache_directory = :domain_cache_directory
private
def domain_cache_directory
Rails.root.join("public", request.domain)
end
end
```
or a callable object:
``` ruby
class DomainCacheDirectory
def self.call(request)
Rails.root.join("public", request.domain)
end
end
class WeblogController < ApplicationController
self.page_cache_directory = DomainCacheDirectory
end
```
Intermediate directories are created as needed also in this case.
### Specify Actions to be Cached
Specifying which actions have to be cached is done through the `caches_page` class method:
``` ruby
class WeblogController < ActionController::Base
caches_page :show, :new
end
```
### Configure The Web Server
The [wiki](https://github.com/rails/actionpack-page_caching/wiki) of the project
has some examples of web server configuration.
### Cache Expiration
Expiration of the cache is handled by deleting the cached files, which results
in a lazy regeneration approach in which the content is stored again as cached
endpoints are hit.
#### Full Cache Expiration
If the cache is stored in a separate directory like `public/cached_pages`, you
can easily expire the whole thing by removing said directory.
Removing a directory recursively with something like `rm -rf` is unreliable
because that operation is not atomic and can mess up with concurrent page cache
generation.
In POSIX systems moving a file is atomic, so the recommended approach would be
to move the directory first out of the way, and then recursively delete that
one. Something like
```bash
#!/bin/bash
tmp=public/cached_pages-$(date +%s)
mv public/cached_pages $tmp
rm -rf $tmp
```
As noted before, the page cache directory is created if it does not exist, so
moving the directory is enough to have a clean cache, no need to recreate.
#### Fine-grained Cache Expiration
The API for doing so mimics the options from `url_for` and friends:
``` ruby
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](https://github.com/rails/rails-observers#action-controller-sweeper)
that act on changes in the model to determine when a cache is supposed to be
expired.
Contributing
------------
1. Fork it.
2. Create your feature branch (`git checkout -b my-new-feature`).
3. Commit your changes (`git commit -am 'Add some feature'`).
4. Push to the branch (`git push origin my-new-feature`).
5. Create a new Pull Request.
Code Status
-----------
* [](https://github.com/rails/actionpack-page_caching/actions/workflows/ci.yml)
actionpack-page_caching-1.2.4/gemfiles/ 0000755 0000041 0000041 00000000000 14054357527 020057 5 ustar www-data www-data actionpack-page_caching-1.2.4/gemfiles/Gemfile-5-2-stable 0000644 0000041 0000041 00000000154 14054357527 023123 0 ustar www-data www-data source "https://rubygems.org"
gemspec path: ".."
gem "rails", github: "rails/rails", branch: "5-2-stable"
actionpack-page_caching-1.2.4/gemfiles/Gemfile-5-0-stable 0000644 0000041 0000041 00000000154 14054357527 023121 0 ustar www-data www-data source "https://rubygems.org"
gemspec path: ".."
gem "rails", github: "rails/rails", branch: "5-0-stable"
actionpack-page_caching-1.2.4/gemfiles/Gemfile-4-2-stable 0000644 0000041 0000041 00000000154 14054357527 023122 0 ustar www-data www-data source "https://rubygems.org"
gemspec path: ".."
gem "rails", github: "rails/rails", branch: "4-2-stable"
actionpack-page_caching-1.2.4/gemfiles/Gemfile-edge 0000644 0000041 0000041 00000000146 14054357527 022255 0 ustar www-data www-data source "https://rubygems.org"
gemspec path: ".."
gem "rails", github: "rails/rails", branch: "main"
actionpack-page_caching-1.2.4/gemfiles/Gemfile-5-1-stable 0000644 0000041 0000041 00000000154 14054357527 023122 0 ustar www-data www-data source "https://rubygems.org"
gemspec path: ".."
gem "rails", github: "rails/rails", branch: "5-1-stable"
actionpack-page_caching-1.2.4/gemfiles/Gemfile-6-0-stable 0000644 0000041 0000041 00000000154 14054357527 023122 0 ustar www-data www-data source "https://rubygems.org"
gemspec path: ".."
gem "rails", github: "rails/rails", branch: "6-0-stable"
actionpack-page_caching-1.2.4/gemfiles/Gemfile-6-1-stable 0000644 0000041 0000041 00000000154 14054357527 023123 0 ustar www-data www-data source "https://rubygems.org"
gemspec path: ".."
gem "rails", github: "rails/rails", branch: "6-1-stable"
actionpack-page_caching-1.2.4/CHANGELOG.md 0000644 0000041 0000041 00000002730 14054357527 020077 0 ustar www-data www-data ## 1.2.4 (May 15, 2021)
- Fix `URI.parser` deprecation warning in Rails 6.1
_Andrew White_
## 1.2.3 (June 12, 2020)
- Simplifies code in `page_caching.rb`
_Xavier Noria_
## 1.2.2 (May 6, 2020)
- Fix variable name
_Jack McCracken_
## 1.2.1 (May 6, 2020)
- Only write relative URIs when their normalized path begins with the normalized cache directory path
_Jack McCracken_
## 1.2.0 (December 11, 2019)
- Update RuboCop config
_Rafael Mendonça França_
- Fix for Rails 6 MIME lookups
_Rob Zolkos_
- Remove Rails 4.2 from testing matrix
_Rob Zolkos_
- Minimum of Ruby 2.4 required
_Rob Zolkos_
- Remove upper dependency for `actionpack`
_Anton Kolodii_
## 1.1.1 (September 25, 2018)
- Fixes handling of several forward slashes as root path
_Xavier Noria_
- Documentation overhaul
_Xavier Noria_
## 1.1.0 (January 23, 2017)
- Support dynamic `page_cache_directory` using a Proc, Symbol or callable
_Andrew White_
- Support instance level setting of `page_cache_directory`
_Andrew White_
- Add support for Rails 5.0 and master
_Andrew White_
## 1.0.2 (November 15, 2013)
- Fix load order problem with other gems.
_Rafael Mendonça França_
## 1.0.1 (October 24, 2013)
- Add Railtie to set `page_cache_directory` by default to `public` folder.
Fixes #5.
_Žiga Vidic_
## 1.0.0 (February 27, 2013)
- Extract Action Pack - Action Caching from Rails core.
_Francesco Rodriguez_, _Rafael Mendonça França_, _Michiel Sikkes_
actionpack-page_caching-1.2.4/.rubocop.yml 0000644 0000041 0000041 00000007460 14054357527 020545 0 ustar www-data www-data AllCops:
TargetRubyVersion: 2.4
# RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
# to ignore them, so only the ones explicitly set in this file are enabled.
DisabledByDefault: true
# Align `when` with `case`.
Layout/CaseIndentation:
Enabled: true
Layout/ClosingHeredocIndentation:
Enabled: true
# Align comments with method definitions.
Layout/CommentIndentation:
Enabled: true
Layout/ElseAlignment:
Enabled: true
Layout/EmptyLineAfterMagicComment:
Enabled: true
Layout/EmptyLinesAroundAccessModifier:
Enabled: true
EnforcedStyle: only_before
Layout/EmptyLinesAroundBlockBody:
Enabled: true
# In a regular class definition, no empty lines around the body.
Layout/EmptyLinesAroundClassBody:
Enabled: true
# In a regular method definition, no empty lines around the body.
Layout/EmptyLinesAroundMethodBody:
Enabled: true
# In a regular module definition, no empty lines around the body.
Layout/EmptyLinesAroundModuleBody:
Enabled: true
# Align `end` with the matching keyword or starting expression except for
# assignments, where it should be aligned with the LHS.
Layout/EndAlignment:
Enabled: true
EnforcedStyleAlignWith: variable
AutoCorrect: true
Layout/FirstArgumentIndentation:
Enabled: true
# Method definitions after `private` or `protected` isolated calls need one
# extra level of indentation.
Layout/IndentationConsistency:
Enabled: true
EnforcedStyle: indented_internal_methods
# Two spaces, no tabs (for indentation).
Layout/IndentationWidth:
Enabled: true
Layout/LeadingCommentSpace:
Enabled: true
Layout/SpaceAfterColon:
Enabled: true
Layout/SpaceAfterComma:
Enabled: true
Layout/SpaceAfterSemicolon:
Enabled: true
Layout/SpaceAroundEqualsInParameterDefault:
Enabled: true
Layout/SpaceAroundKeyword:
Enabled: true
# Use `foo {}` not `foo{}`.
Layout/SpaceBeforeBlockBraces:
Enabled: true
Layout/SpaceBeforeComma:
Enabled: true
Layout/SpaceBeforeComment:
Enabled: true
Layout/SpaceBeforeFirstArg:
Enabled: true
# Use `foo { bar }` not `foo {bar}`.
Layout/SpaceInsideBlockBraces:
Enabled: true
EnforcedStyleForEmptyBraces: space
# Use `{ a: 1 }` not `{a:1}`.
Layout/SpaceInsideHashLiteralBraces:
Enabled: true
Layout/SpaceInsideParens:
Enabled: true
# Detect hard tabs, no hard tabs.
Layout/Tab:
Enabled: true
# Empty lines should not have any spaces.
Layout/TrailingEmptyLines:
Enabled: true
# No trailing whitespace.
Layout/TrailingWhitespace:
Enabled: true
Lint/AmbiguousOperator:
Enabled: true
Lint/AmbiguousRegexpLiteral:
Enabled: true
Lint/DeprecatedClassMethods:
Enabled: true
Lint/ErbNewArguments:
Enabled: true
Lint/RedundantStringCoercion:
Enabled: true
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
Lint/RequireParentheses:
Enabled: true
Lint/ShadowingOuterLocalVariable:
Enabled: true
Lint/UselessAssignment:
Enabled: true
Lint/UriEscapeUnescape:
Enabled: true
# Prefer &&/|| over and/or.
Style/AndOr:
Enabled: true
# Prefer Foo.method over Foo::method
Style/ColonMethodCall:
Enabled: true
Style/DefWithParentheses:
Enabled: true
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
Style/HashSyntax:
Enabled: true
# Defining a method with parameters needs parentheses.
Style/MethodDefParentheses:
Enabled: true
Style/ParenthesesAroundCondition:
Enabled: true
Style/RedundantBegin:
Enabled: true
Style/RedundantFreeze:
Enabled: true
# Use quotes for string literals when they are enough.
Style/RedundantPercentQ:
Enabled: true
Style/RedundantReturn:
Enabled: true
AllowMultipleReturnValues: true
Style/Semicolon:
Enabled: true
AllowAsExpressionSeparator: true
# Check quotes usage according to lint rule below.
Style/StringLiterals:
Enabled: true
EnforcedStyle: double_quotes
Style/TrivialAccessors:
Enabled: true
actionpack-page_caching-1.2.4/.gitignore 0000644 0000041 0000041 00000000072 14054357527 020253 0 ustar www-data www-data .ruby-version
Gemfile.lock
gemfiles/*.lock
pkg/*
test/tmp
actionpack-page_caching-1.2.4/.codeclimate.yml 0000644 0000041 0000041 00000000105 14054357527 021332 0 ustar www-data www-data engines:
rubocop:
enabled: true
ratings:
paths:
- "**.rb"
actionpack-page_caching-1.2.4/Rakefile 0000644 0000041 0000041 00000000311 14054357527 017724 0 ustar www-data www-data #!/usr/bin/env rake
require "bundler/gem_tasks"
require "rake/testtask"
Rake::TestTask.new do |t|
t.libs = ["test"]
t.pattern = "test/**/*_test.rb"
t.ruby_opts = ["-w"]
end
task default: :test
actionpack-page_caching-1.2.4/lib/ 0000755 0000041 0000041 00000000000 14054357527 017032 5 ustar www-data www-data actionpack-page_caching-1.2.4/lib/actionpack/ 0000755 0000041 0000041 00000000000 14054357527 021146 5 ustar www-data www-data actionpack-page_caching-1.2.4/lib/actionpack/page_caching/ 0000755 0000041 0000041 00000000000 14054357527 023536 5 ustar www-data www-data actionpack-page_caching-1.2.4/lib/actionpack/page_caching/railtie.rb 0000644 0000041 0000041 00000000767 14054357527 025526 0 ustar www-data www-data require "rails/railtie"
module ActionPack
module PageCaching
class Railtie < Rails::Railtie
initializer "action_pack.page_caching" do
ActiveSupport.on_load(:action_controller) do
require "action_controller/page_caching"
end
end
initializer "action_pack.page_caching.set_config", before: "action_controller.set_configs" do |app|
app.config.action_controller.page_cache_directory ||= app.config.paths["public"].first
end
end
end
end
actionpack-page_caching-1.2.4/lib/actionpack/page_caching.rb 0000644 0000041 0000041 00000000052 14054357527 024060 0 ustar www-data www-data require "actionpack/page_caching/railtie"
actionpack-page_caching-1.2.4/lib/action_controller/ 0000755 0000041 0000041 00000000000 14054357527 022552 5 ustar www-data www-data actionpack-page_caching-1.2.4/lib/action_controller/caching/ 0000755 0000041 0000041 00000000000 14054357527 024146 5 ustar www-data www-data actionpack-page_caching-1.2.4/lib/action_controller/caching/pages.rb 0000644 0000041 0000041 00000025711 14054357527 025600 0 ustar www-data www-data require "fileutils"
require "uri"
require "active_support/core_ext/class/attribute_accessors"
require "active_support/core_ext/string/strip"
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 weblog/show/5.html and
# weblog/new.html, 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 Base.page_cache_directory = "/document/root". For Rails,
# this directory has already been set to Rails.public_path (which is usually
# set to Rails.root + "/public"). Changing this setting can be useful
# to avoid naming conflicts with files in public/, 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 ||= ""
# 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
class PageCache #:nodoc:
def initialize(cache_directory, default_extension, controller = nil)
@cache_directory = cache_directory
@default_extension = default_extension
@controller = controller
end
def expire(path)
instrument :expire_page, path do
delete(cache_path(path))
end
end
def cache(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
instrument :write_page, path do
write(content, cache_path(path, extension), gzip)
end
end
private
def cache_directory
case @cache_directory
when Proc
handle_proc_cache_directory
when Symbol
handle_symbol_cache_directory
else
handle_default_cache_directory
end
end
def normalized_cache_directory
File.expand_path(cache_directory)
end
def handle_proc_cache_directory
if @controller
@controller.instance_exec(&@cache_directory)
else
raise_runtime_error
end
end
def handle_symbol_cache_directory
if @controller
@controller.send(@cache_directory)
else
raise_runtime_error
end
end
def handle_callable_cache_directory
if @controller
@cache_directory.call(@controller.request)
else
raise_runtime_error
end
end
def handle_default_cache_directory
if @cache_directory.respond_to?(:call)
handle_callable_cache_directory
else
@cache_directory.to_s
end
end
def raise_runtime_error
raise RuntimeError, <<-MSG.strip_heredoc
Dynamic page_cache_directory used with class-level cache_page method
You have specified either a Proc, Symbol or callable object for page_cache_directory
which needs to be executed within the context of a request. If you need to call the
cache_page method from a class-level context then set the page_cache_directory to a
static value and override the setting at the instance-level using before_action.
MSG
end
attr_reader :default_extension
def cache_file(path, extension)
if path.empty? || path =~ %r{\A/+\z}
name = "/index"
else
name = URI::DEFAULT_PARSER.unescape(path.chomp("/"))
end
if File.extname(name).empty?
name + (extension || default_extension)
else
name
end
end
def cache_path(path, extension = nil)
unnormalized_path = File.join(normalized_cache_directory, cache_file(path, extension))
normalized_path = File.expand_path(unnormalized_path)
normalized_path if normalized_path.start_with?(normalized_cache_directory)
end
def delete(path)
return unless path
File.delete(path) if File.exist?(path)
File.delete(path + ".gz") if File.exist?(path + ".gz")
end
def write(content, path, gzip)
return unless path
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
def instrument(name, path)
ActiveSupport::Notifications.instrument("#{name}.action_controller", path: path) { yield }
end
end
module ClassMethods
# Expires the page that was cached with the +path+ as a key.
#
# expire_page "/lists/show"
def expire_page(path)
if perform_caching
page_cache.expire(path)
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)
if perform_caching
page_cache.cache(content, path, extension, gzip)
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.
#
# # 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)
if 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 Integer
gzip_level
when false
nil
else
Zlib::BEST_COMPRESSION
end
after_action({ only: actions }.merge(options)) do |c|
c.cache_page(nil, nil, gzip_level)
end
end
end
private
def page_cache
PageCache.new(page_cache_directory, default_static_extension)
end
end
# Expires the page that was cached with the +options+ as a key.
#
# expire_page controller: "lists", action: "show"
def expire_page(options = {})
if perform_caching?
case options
when Hash
case options[:action]
when Array
options[:action].each { |action| expire_page(options.merge(action: action)) }
else
page_cache.expire(url_for(options.merge(only_path: true)))
end
else
page_cache.expire(options)
end
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)
if 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
type = if self.respond_to?(:media_type)
Mime::LOOKUP[self.media_type]
else
Mime::LOOKUP[self.content_type]
end
if type && (type_symbol = type.symbol).present?
extension = ".#{type_symbol}"
end
page_cache.cache(content || response.body, path, extension, gzip)
end
end
def caching_allowed?
(request.get? || request.head?) && response.status == 200
end
def perform_caching?
self.class.perform_caching
end
private
def page_cache
PageCache.new(page_cache_directory, default_static_extension, self)
end
end
end
end
actionpack-page_caching-1.2.4/lib/action_controller/page_caching.rb 0000644 0000041 0000041 00000000262 14054357527 025467 0 ustar www-data www-data require "action_controller/caching/pages"
module ActionController
module Caching
include Pages
end
end
ActionController::Base.include(ActionController::Caching::Pages)
actionpack-page_caching-1.2.4/Gemfile 0000644 0000041 0000041 00000000137 14054357527 017560 0 ustar www-data www-data source "https://rubygems.org"
gemspec
gem "rails"
gem "rubocop", ">= 0.77.0", require: false
actionpack-page_caching-1.2.4/.github/ 0000755 0000041 0000041 00000000000 14054357527 017624 5 ustar www-data www-data actionpack-page_caching-1.2.4/.github/workflows/ 0000755 0000041 0000041 00000000000 14054357527 021661 5 ustar www-data www-data actionpack-page_caching-1.2.4/.github/workflows/ci.yml 0000644 0000041 0000041 00000003351 14054357527 023001 0 ustar www-data www-data name: CI
on:
push:
branches:
- 'master'
pull_request:
jobs:
build:
strategy:
matrix:
gemfile:
- '4-2-stable'
- '5-0-stable'
- '5-1-stable'
- '5-2-stable'
- '6-0-stable'
- '6-1-stable'
- 'edge'
ruby:
- '2.4'
- '2.5'
- '2.6'
- '2.7'
- '3.0'
exclude:
- gemfile: '4-2-stable'
ruby: '2.5'
- gemfile: '4-2-stable'
ruby: '2.6'
- gemfile: '4-2-stable'
ruby: '2.7'
- gemfile: '4-2-stable'
ruby: '3.0'
- gemfile: '5-0-stable'
ruby: '2.7'
- gemfile: '5-0-stable'
ruby: '3.0'
- gemfile: '5-1-stable'
ruby: '2.7'
- gemfile: '5-1-stable'
ruby: '3.0'
- gemfile: '5-2-stable'
ruby: '2.7'
- gemfile: '5-2-stable'
ruby: '3.0'
- gemfile: '6-0-stable'
ruby: '2.4'
- gemfile: '6-1-stable'
ruby: '2.4'
- gemfile: 'edge'
ruby: '2.4'
- gemfile: 'edge'
ruby: '2.5'
- gemfile: 'edge'
ruby: '2.6'
fail-fast: false
runs-on: ubuntu-latest
name: ${{ matrix.ruby }} rails-${{ matrix.gemfile }}
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
bundler: ${{ fromJSON('["2", "1"]')[matrix.ruby == '2.4'] }}
- run: bundle exec rake test
env:
BUNDLE_GEMFILE: gemfiles/Gemfile-${{ matrix.gemfile }}
BUNDLE_JOBS: 4
BUNDLE_RETRY: 3
actionpack-page_caching-1.2.4/actionpack-page_caching.gemspec 0000644 0000041 0000041 00000001553 14054357527 024337 0 ustar www-data www-data Gem::Specification.new do |gem|
gem.name = "actionpack-page_caching"
gem.version = "1.2.4"
gem.author = "David Heinemeier Hansson"
gem.email = "david@loudthinking.com"
gem.description = "Static page caching for Action Pack (removed from core in Rails 4.0)"
gem.summary = "Static page caching for Action Pack (removed from core in Rails 4.0)"
gem.homepage = "https://github.com/rails/actionpack-page_caching"
gem.license = "MIT"
gem.required_ruby_version = ">= 1.9.3"
gem.files = `git ls-files`.split($/)
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ["lib"]
gem.license = "MIT"
gem.add_dependency "actionpack", ">= 4.0.0"
gem.add_development_dependency "mocha"
end
actionpack-page_caching-1.2.4/LICENSE.txt 0000644 0000041 0000041 00000002071 14054357527 020107 0 ustar www-data www-data Copyright (c) 2012 David Heinemeier Hansson
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.