Page caching is a coarse approach to caching whereby an entire page is rendered once and stored on the web server so that subsequent requests don’t even reach the application server.
While this approach offers a significant performance boost, it’s only appropriate for stateless pages where all visitors are treated the same.
Examples: Weblogs, wikis, static pages. Anything that doesn’t change based on, say, whether or not the user is logged in.
Nonexamples: Pages requiring authentication for any other
(since requests never reaches a
before_filter). Pages that
are rendered differently for different users.
In Rails 4, page caching must be enabled via the
# config/environments/development.rb config.action_controller.perform_caching = false # enables caching locally
# Gemfile gem 'actionpack-page_caching'
# config/application.rb config.action_controller.page_cache_directory = Rails.root.join('public', 'cache')
And declare which actions render templates that can be cached in their entirety:
# app/controllers/posts_controller.rb class PostsController < ActionController::Base caches_page :index, :show # . . . end
To expire a cached page, call
expire_page whenever any of its data becomes
outdated. For example,
class PostsController < ActionController::Base caches_page :index, :show after_action :clear_posts_cache, only: %i(index show) # . . . private def clear_posts_cache expire_page action: :index expire_page action: :show, id: @post end end
But what happens when you add a comments controller and need to expire a post’s
page cache when a new comment on it is created? You could move the defintion of
clear_posts_cache one level up to
ApplicationController or to a namespace
both have access to, such as a concern.
A cleaner, more object-oriented way is to use a Sweeper. These too have been removed from the Rails core with Rails 4.0, so a bundle install is in order:
# Gemfile gem 'rails-observers'
Sweepers are responsible for observing models and expiring caches when an assigned model’s attributes change value.
# app/sweepers/post_sweeper.rb class PostSweeper < ActionController::Caching::Sweeper observe Post, Comment def after_save(record) post = post_from(record) clear_posts_cache_for(post) end def after_destroy(record) post = post_from(record) clear_posts_cache_for(post) end private def clear_posts_cache_for(post) expire_page(controller: :posts, action: %i(index show), id: post end def post_from(record) record.is_a?(Post) ? record : record.post end end
Note that any subdirectories of
app belong automatically to
Note also that our sweeper hooks here are
but we can use any ActiveRecord callbacks.
You can then call this Sweeper via a callback in any controllers that might change the pages being cached:
class PostsController < ApplicationController cache_sweeper :post_sweeper, only: %i(edit destroy) end
class CommentsController < ApplicationController cache_sweeper :post_sweeper, only: %i(create edit destroy) end
One obvious, if slightly iffy, way to make page caching work with pages that have some kind of state is to use AJAX calls to dynamically update only the required data on the page. This might be fine if it’s a small amount of data (say, a login link), but in most cases there are better approaches to take.
So what happens when you are working with pages that need to hit controller filters? That’s where our next strategy comes in: action caching. More on that next time.
These notes were compiled from the Rails guides.