15.4. Page Caching

Page Caching is extrem and was removed from the core of Rails 4.0. But it is still available as a gem.

Buy the new Rails 5.1 version of this book.

You need quite a bit of knowledge to configure your Webserver (e.g. Nginx or Apache).
With page caching, it's all about placing a complete HTML page (in other words, the render result of a view) into a subdirectory of the public directory and to have it delivered directly from there by the web server (for example Nginx) whenever the web page is visited next. Additionally, you can also save a compressed gz version of the HTML page there. A production web server will automatically deliver files below public itself and can also be configured so that any gz files present are delivered directly.
In complex views that may take 500ms or even more for rendering, the amount of time you save is of course considerable. As web page operator, you once more save valuable server resources and can service more visitors with the same hardware. The web page user profits from a faster delivery of the web page.

Buy the new Rails 5.1 version of this book.

When programming your Rails application, please ensure that you also update this page itself, or delete it! You will find a description in the section called “Deleting Page Caches Automatically”. Otherwise, you end up with an outdated cache later.
Please also ensure that page caching rejects all URL parameters by default. For example, if you try to go to http://0.0.0.0:3000/companies?search=abc this automatically becomes http://0.0.0.0:3000/companies. But that can easily be fixed with a better route logic.
Please install a fresh example application (see the section called “A Simple Example Application”) and add the gem with the following line in Gemfile.
gem 'actionpack-page_caching'
Now install it with the command bundle install.
$ bundle install
[...]
$
Lastly you have to tell Rails where to store the cache files. Please add the following line in your config/application.rb file:
config.action_controller.page_cache_directory = "#{Rails.root.to_s}/public/deploy"

Activating Page Caching in Development Mode

First we need to go to the file config/environments/development.rb and set the item config.action_controller.perform_caching to true:
config.action_controller.perform_caching = true
Otherwise, we cannot try the page caching in development mode. In production mode, page caching is enabled by default.

Configure our Webserver

Know you have to tell your webserver (e.g. Nginx or Apache) that it should check the /public/deploy directory first before hitting the Rails application. You have to configure too, that it will deliver a gz file if one is available.
There is no one perfect way of doing it. You have to find the best way of doing it in your environment by youself.

Buy the new Rails 5.1 version of this book.

As a quick and dirty hack for development you can set the page_cache_directory to public. Than your development system will deliver the cached page.
config.action_controller.page_cache_directory = "#{Rails.root.to_s}/public"

Caching Company Index and Show View

Enabling page caching happens in the controller. If we want to cache the show view for Company, we need to go to the controller app/controllers/companies_controller.rb and enter the command caches_page :show at the top:
class CompaniesController < ApplicationController
  caches_page :show

[...]
Before starting the application, the public directory looks like this:
public/
├── 404.html
├── 422.html
├── 500.html
├── favicon.ico
└── robots.txt
After starting the appliation with rails server and going to the URLs http://0.0.0.0:3000/companies and http://0.0.0.0:3000/companies/1 via a web browser, it looks like this:
public
├── 404.html
├── 422.html
├── 500.html
├── deploy
│   └── companies
│       └── 1.html
├── favicon.ico
└── robots.txt
The file public/deploy/companies/1.html has been created by page caching.
From now on, the web server will only deliver the cached versions when these pages are accessed.

gz Versions

If you use page cache, you should also cache directly zipped gz files. You can do this via the option :gzip => true or use a specific compression parameter as symbol instead of true (for example :best_compression).
The controller app/controllers/companies_controller.rb would then look like this at the beginning:
class CompaniesController < ApplicationController
  caches_page :show, gzip: true

[...]
This automatically saves a compressed and an uncompressed version of each page cache:
public
├── 404.html
├── 422.html
├── 500.html
├── deploy
│   └── companies
│       ├── 1.html
│       └── 1.html.gz
├── favicon.ico
└── robots.txt

The File Extension .html

Rails saves the page accessed at http://0.0.0.0:3000/companies under the file name companies.html. So the upstream web server will find and deliver this file if you go to http://0.0.0.0:3000/companies.html, but not if you try to go to http://0.0.0.0:3000/companies, because the extension .html at the end of the URI is missing.
If you are using the Nginx server as described in Chapter 16, Web Server in Production Mode, the easiest way is adapting the try_files instruction in the Nginx configuration file as follows:
try_files $uri/index.html $uri $uri.html @unicorn;
Nginx then checks if a file with the extension .html of the currently accessed URI exists.

Deleting Page Caches Automatically

As soon as the data used in the view changes, the saved cache files have to be deleted. Otherwise, the cache would no longer be up to date.
According to the official Rails documentation, the solution for this problem is the class ActionController::Caching::Sweeper. But this approach, described at http://guides.rubyonrails.org/caching_with_rails.html#sweepers, has a big disadvantage: it is limited to actions that happen within the controller. So if an action is triggered via URL by the web browser, the corresponding cache is also changed or deleted. But if an object is deleted in the console, for example, the sweeper would not realize this. For that reason, I am going to show you an approach that does not use a sweeper, but works directly in the model with ActiveRecord callbacks.
In our phone book application, we always need to delete the cache for http://0.0.0.0:3000/companies and http://0.0.0.0:3000/companies/company_id when editing a company. When editing an employee, we also have to delete the corresponding cache for the relevant employee.

Models

Now we still need to fix the models so that the corresponding caches are deleted automatically as soon as an object is created, edited or deleted.
app/models/company.rb
class Company < ActiveRecord::Base
  validates :name,
            presence: true,
            uniqueness: true

  has_many :employees, dependent: :destroy

  after_create   :expire_cache
  after_update   :expire_cache
  before_destroy :expire_cache

  def to_s
    name
  end

  def expire_cache
    ActionController::Base.expire_page(Rails.application.routes.url_helpers.company_path(self))
    ActionController::Base.expire_page(Rails.application.routes.url_helpers.companies_path)
  end

end
app/models/employee.rb
class Employee < ActiveRecord::Base
  belongs_to :company, touch: true

  validates :first_name,
            presence: true

  validates :last_name,
            presence: true

  validates :company,
            presence: true

  after_create   :expire_cache
  after_update   :expire_cache
  before_destroy :expire_cache

  def to_s
    "#{first_name} #{last_name}"
  end

  def expire_cache
    ActionController::Base.expire_page(Rails.application.routes.url_helpers.employee_path(self))
    ActionController::Base.expire_page(Rails.application.routes.url_helpers.employees_path)
    self.company.expire_cache
  end

end

Thank you for your support and the visibility by linking to this website on Twitter and Facebook. That helps a lot!