actionpack-page_caching-1.1.0/0000755000004100000410000000000013314504367016251 5ustar www-datawww-dataactionpack-page_caching-1.1.0/.travis.yml0000644000004100000410000000256213314504367020367 0ustar www-datawww-datalanguage: ruby sudo: false cache: bundler: true before_install: - gem install bundler rvm: - 1.9.3 - 2.0.0 - 2.1.9 - 2.2.6 - 2.3.3 - 2.4.0 gemfile: - Gemfile - gemfiles/Gemfile-4-0-stable - gemfiles/Gemfile-4-1-stable - gemfiles/Gemfile-4-2-stable - gemfiles/Gemfile-5-0-stable - gemfiles/Gemfile-edge matrix: allow_failures: - gemfile: gemfiles/Gemfile-edge exclude: - rvm: 1.9.3 gemfile: Gemfile - rvm: 2.0.0 gemfile: Gemfile - rvm: 2.1.9 gemfile: Gemfile - rvm: 1.9.3 gemfile: gemfiles/Gemfile-5-0-stable - rvm: 2.0.0 gemfile: gemfiles/Gemfile-5-0-stable - rvm: 2.1.9 gemfile: gemfiles/Gemfile-5-0-stable - rvm: 1.9.3 gemfile: gemfiles/Gemfile-edge - rvm: 2.0.0 gemfile: gemfiles/Gemfile-edge - rvm: 2.1.9 gemfile: gemfiles/Gemfile-edge - rvm: 2.4.0 gemfile: gemfiles/Gemfile-4-0-stable - rvm: 2.4.0 gemfile: gemfiles/Gemfile-4-1-stable notifications: email: false irc: on_success: change on_failure: always channels: - "irc.freenode.org#rails-contrib" campfire: on_success: change on_failure: always rooms: - secure: "eRCx+FMvH50pmLu0GZTF7NN+2X+CesgodYUlHvCr5EXQ0ZO/YUmeW8vAh/N8\njSrLWYpk/4P/JA63JGWsvFor/zpkTnfwzX3LWgw04GV0V3T9jsn9CD2Coiu6\nFll5u4fUCUwpfbB4RlCkjvFdQmW+F9mmbRGMCDO5CmuPHOyyPH0=" actionpack-page_caching-1.1.0/test/0000755000004100000410000000000013314504367017230 5ustar www-datawww-dataactionpack-page_caching-1.1.0/test/abstract_unit.rb0000644000004100000410000000052613314504367022422 0ustar www-datawww-datarequire "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.1.0/test/caching_test.rb0000644000004100000410000003222713314504367022216 0ustar www-datawww-datarequire "abstract_unit" require "mocha/setup" 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_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 begin 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 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.1.0/test/log_subscriber_test.rb0000644000004100000410000000254713314504367023630 0ustar www-datawww-datarequire "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.1.0/README.md0000644000004100000410000000754113314504367017537 0ustar www-datawww-dataactionpack-page_caching ======================= Static page caching for Action Pack (removed from core in Rails 4.0). Installation ------------ Add this line to your application's Gemfile: ``` ruby gem "actionpack-page_caching" ``` And then execute: $ bundle Or install it yourself as: $ gem install actionpack-page_caching Usage ----- Page caching is an approach to caching where the entire action output 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. First you need to set `page_cache_directory` in your configuration file: ``` ruby config.action_controller.page_cache_directory = "#{Rails.root}/public/cached_pages" ``` The `page_cache_directory` setting can be used with a Proc: ``` 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 ``` Specifying which actions to cache is done through the `caches_page` class method: ``` ruby 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: ``` 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. Finally, configure your web server to serve these static pages when they are present rather than the original files. See the [project wiki][1] for example configurations. [1]: https://github.com/rails/actionpack-page_caching/wiki 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 ----------- * [![Build Status](https://travis-ci.org/rails/actionpack-page_caching.svg?branch=master)](https://travis-ci.org/rails/actionpack-page_caching) * [![Dependency Status](https://gemnasium.com/rails/actionpack-page_caching.svg)](https://gemnasium.com/rails/actionpack-page_caching) actionpack-page_caching-1.1.0/gemfiles/0000755000004100000410000000000013314504367020044 5ustar www-datawww-dataactionpack-page_caching-1.1.0/gemfiles/Gemfile-4-1-stable0000644000004100000410000000020413314504367023102 0ustar www-datawww-datasource "https://rubygems.org" gemspec path: ".." gem "rails", github: "rails/rails", branch: "4-1-stable" gem "mime-types", "< 3" actionpack-page_caching-1.1.0/gemfiles/Gemfile-5-0-stable0000644000004100000410000000015413314504367023106 0ustar www-datawww-datasource "https://rubygems.org" gemspec path: ".." gem "rails", github: "rails/rails", branch: "5-0-stable" actionpack-page_caching-1.1.0/gemfiles/Gemfile-4-2-stable0000644000004100000410000000027313314504367023111 0ustar www-datawww-datasource "https://rubygems.org" gemspec path: ".." gem "rails", github: "rails/rails", branch: "4-2-stable" gem "mime-types", "< 3" if RUBY_VERSION < "2.1" gem "nokogiri", "< 1.7" end actionpack-page_caching-1.1.0/gemfiles/Gemfile-edge0000644000004100000410000000023313314504367022237 0ustar www-datawww-datasource "https://rubygems.org" gemspec path: ".." gem "rails", github: "rails/rails", branch: "master" gem "arel", github: "rails/arel", branch: "master" actionpack-page_caching-1.1.0/gemfiles/Gemfile-4-0-stable0000644000004100000410000000020413314504367023101 0ustar www-datawww-datasource "https://rubygems.org" gemspec path: ".." gem "rails", github: "rails/rails", branch: "4-0-stable" gem "mime-types", "< 3" actionpack-page_caching-1.1.0/CHANGELOG.md0000644000004100000410000000125213314504367020062 0ustar www-datawww-data## 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.1.0/.rubocop.yml0000644000004100000410000000507013314504367020525 0ustar www-datawww-dataAllCops: TargetRubyVersion: 2.2 # 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 # Prefer &&/|| over and/or. Style/AndOr: Enabled: true # Do not use braces for hash literals when they are the last argument of a # method call. Style/BracesAroundHashParameters: Enabled: true # Align `when` with `case`. Style/CaseIndentation: Enabled: true # Align comments with method definitions. Style/CommentIndentation: Enabled: true # No extra empty lines. Style/EmptyLines: Enabled: true # In a regular class definition, no empty lines around the body. Style/EmptyLinesAroundClassBody: Enabled: true # In a regular module definition, no empty lines around the body. Style/EmptyLinesAroundModuleBody: Enabled: true # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. Style/HashSyntax: Enabled: true # Method definitions after `private` or `protected` isolated calls need one # extra level of indentation. Style/IndentationConsistency: Enabled: true EnforcedStyle: rails # Two spaces, no tabs (for indentation). Style/IndentationWidth: Enabled: true Style/SpaceAfterColon: Enabled: true Style/SpaceAfterComma: Enabled: true Style/SpaceAroundEqualsInParameterDefault: Enabled: true Style/SpaceAroundKeyword: Enabled: true Style/SpaceAroundOperators: Enabled: true Style/SpaceBeforeFirstArg: Enabled: true # Defining a method with parameters needs parentheses. Style/MethodDefParentheses: Enabled: true # Use `foo {}` not `foo{}`. Style/SpaceBeforeBlockBraces: Enabled: true # Use `foo { bar }` not `foo {bar}`. Style/SpaceInsideBlockBraces: Enabled: true # Use `{ a: 1 }` not `{a:1}`. Style/SpaceInsideHashLiteralBraces: Enabled: true Style/SpaceInsideParens: Enabled: true # Check quotes usage according to lint rule below. Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes # Detect hard tabs, no hard tabs. Style/Tab: Enabled: true # Blank lines should not have any spaces. Style/TrailingBlankLines: Enabled: true # No trailing whitespace. Style/TrailingWhitespace: Enabled: true # Use quotes for string literals when they are enough. Style/UnneededPercentQ: Enabled: true # Align `end` with the matching keyword or starting expression except for # assignments, where it should be aligned with the LHS. Lint/EndAlignment: Enabled: true AlignWith: variable # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. Lint/RequireParentheses: Enabled: true actionpack-page_caching-1.1.0/.gitignore0000644000004100000410000000007213314504367020240 0ustar www-datawww-data.ruby-version Gemfile.lock gemfiles/*.lock pkg/* test/tmp actionpack-page_caching-1.1.0/.codeclimate.yml0000644000004100000410000000010513314504367021317 0ustar www-datawww-dataengines: rubocop: enabled: true ratings: paths: - "**.rb" actionpack-page_caching-1.1.0/Rakefile0000644000004100000410000000031113314504367017711 0ustar www-datawww-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.1.0/lib/0000755000004100000410000000000013314504367017017 5ustar www-datawww-dataactionpack-page_caching-1.1.0/lib/actionpack/0000755000004100000410000000000013314504367021133 5ustar www-datawww-dataactionpack-page_caching-1.1.0/lib/actionpack/page_caching/0000755000004100000410000000000013314504367023523 5ustar www-datawww-dataactionpack-page_caching-1.1.0/lib/actionpack/page_caching/railtie.rb0000644000004100000410000000076713314504367025513 0ustar www-datawww-datarequire "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.1.0/lib/actionpack/page_caching.rb0000644000004100000410000000005213314504367024045 0ustar www-datawww-datarequire "actionpack/page_caching/railtie" actionpack-page_caching-1.1.0/lib/action_controller/0000755000004100000410000000000013314504367022537 5ustar www-datawww-dataactionpack-page_caching-1.1.0/lib/action_controller/caching/0000755000004100000410000000000013314504367024133 5ustar www-datawww-dataactionpack-page_caching-1.1.0/lib/action_controller/caching/pages.rb0000644000004100000410000002477413314504367025575 0ustar www-datawww-datarequire "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 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 def default_extension @default_extension end def cache_file(path, extension) if path.empty? || path == "/" name = "/index" else name = URI.parser.unescape(path.chomp("/")) end if File.extname(name).empty? name + (extension || default_extension) else name end end def cache_path(path, extension = nil) File.join(cache_directory, cache_file(path, extension)) end def delete(path) File.delete(path) if File.exist?(path) File.delete(path + ".gz") if File.exist?(path + ".gz") end def write(content, path, gzip) 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 if (type = Mime::LOOKUP[self.content_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.1.0/lib/action_controller/page_caching.rb0000644000004100000410000000035613314504367025460 0ustar www-datawww-datarequire "action_controller/caching/pages" module ActionController module Caching eager_autoload do autoload :Pages end include Pages end end ActionController::Base.send(:include, ActionController::Caching::Pages) actionpack-page_caching-1.1.0/Gemfile0000644000004100000410000000006413314504367017544 0ustar www-datawww-datasource "https://rubygems.org" gemspec gem "rails" actionpack-page_caching-1.1.0/actionpack-page_caching.gemspec0000644000004100000410000000156213314504367024324 0ustar www-datawww-dataGem::Specification.new do |gem| gem.name = "actionpack-page_caching" gem.version = "1.1.0" 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", "< 6" gem.add_development_dependency "mocha" end actionpack-page_caching-1.1.0/LICENSE.txt0000644000004100000410000000207113314504367020074 0ustar www-datawww-dataCopyright (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.