This is an introduction to caching in Rails. You may know that Rails performs some caching “automagically”, but you aren’t quite sure what that means or what that is. Read on to find out. In a nutshell, caching gives us quick access to values without hitting a more expensive store, such as a database. In this manner, we can improve the performance of our Rails app. Rails will cache almost anything, from an object to a page to a partial. Rails mainly provide three techniques of caching.
- Page caching
- Action caching
- Fragment caching
config.action_controller.perform_caching = true
It’s true by default for production.rb
but false for development.rb
and test.rb
.
Page Caching
Page Caching is a mechanism which stores the output of an action in a file. so when a request comes in the web server can respond without going through Actionpack . To try this out, I have created a sample application . I generated a scaffold blog with title and description attributes. In our blogs controller, we have an index action and we want to cache that. To accomplish this, call caches_page method for the action we wish to cache.class BlogsController < ApplicationController
caches_page :index
def index
@blogs = Blog.all
end
end
Rails create a file in the /public directory called blogs.html When we start our server and make a request for ‘/blogs’, Rails caches the first vist to the page in the aforementioned blogs.html file. Any time after that, you will see that cached file, as shown in the following log entry:
Write page /Users/rashmiyadav/crap/cache_digest_test/public/blogs.html (0.7ms)
If you want to cache another action, just add it to method caches_page
caches_page :index, :show
In order to expire the page so we can cache a new version, issue a call to expire_page:
class BlogsController < ApplicationController
caches_page :index
def index
@blogs = Blog.all
end
def create
expire_page :action => :index
@blog = Blog.new(params[:blog])
@blog.save
end
def update
expire_page :action => :index
@blog = Blog.find(params[:id])
@blog.update_attributes(params[:blog])
end
end
Anytime we update or create a record it will expire the index cache. You can validate this, again, using the logs :
Expire page /Users/rashmiyadav/crap/cache_digest_test/public/cache/blogs.html (0.2ms)
The expire_page method takes a number of arguments, such as the path of cached page like ‘/blogs’, or a hash with action, controller name, and format to expire.
expire_page :controller => 'blogs', :action => 'show', :format => 'json'
Configuration
By default, Rails stores cache files in the /public directory, but we can change it. Simple change the page_cache_directory value in config/application.rb file:
config.action_controller.page_cache_directory = Rails.public_path + "/cache"
Now all cache files will be in the /public/cache directory. One thing to remember, if you are running the server locally in development and you have old cache files in you public directory, the server will continue to pick up the old cache files in the public directory. In other words, don’t forget to remove the old cache files from the public directory.
Another configuration item for page caching is the extension of the cache file. The extension of a cache file depends on the incoming request. If it’s json, then cache file will be ‘.json’ and if the request is xml then the cache file will be ‘.xml’ and so on. If a request does not have any specific extension then, by default , it will get a ‘.html’ extension. We can configure this using config.action_controller.page_cache_extension.
Page Caching is one way to speed up your application, but it is not an option if you have content that sits behind authentication. Let’s look at the other methods of caching in Rails.
Action Caching
Action Caching is similar to Page Caching, except that filters are executed before the caching occurs. Let’s take our blog application example:class BlogsController < ApplicationController
before_filter :authenticate
caches_action :index
def index
@blogs = Blog.all
end
end
we call caches_action in order to cache the index action. The first time we hit the link http://localhost:3000/blogs results in the following blog entry :
Write fragment views/localhost:3000/blogs (1.8ms)
Now, every time we call the index action, it will run the authenticate method before giving the cached response. This is the main difference between action and page caching . Action caching runs all the filters before serving cache.
Expire the Cache
To expire the cache, we just need to call expire_action:class BlogsController < ApplicationController
caches_action :index
def index
@blogs = Blog.all
end
def create
expire_action :action => :index
@blog = Blog.new(params[:blog])
@blog.save
end
def update
expire_action :action => :index
@blog = Blog.find(params[:id])
@blog.update_attributes(params[:blog])
end
end
When we update and create a record, it will expire the action cache. Here’s the corresponding log entry:
Expire fragment views/localhost:3000/blogs (59.0ms)
The expire_action method takes the same arguments as expire_page above:
expire_action(:controller => :blogs, :action => :show, :id => 25)
Configuration
If we need to cache an action based on some conditions, we can do that by using :if or :unless with a proc. For example, if we don’t want to cache the index action for json requests:class BlogsController > ApplicationController
caches_action :index, :if => Proc.new{|c|!c.request.format.json?}
def index
@blogs = Blog.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @blogs }
format.csv
end
end
end
We can also configure the default cache path.
caches_action :show, :cache_path => { :project => 1 }
If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information .
Fragment Caching
Page caching and action caching both cache an entire page or an entire action, but what if we just need to cache a part of a view? This is where fragment caching comes into play. We usually have a view with dynamic content, so we can’t store the whole page. However, it is likely that we can cache bits of the page and expire them as needed. If we are showing a blog post with their comments, we can cache the comments by themselves:<div>
<%= @blog.title %><%= @blog.desc %>
</div>
<%cache("blog_comments_#{@blog.id}") do%>
<h3>Comments</h3>
<% @blog.comments.each_with_index do|c, i|%>
<div><%= c.value%><span><%= c.created_at.strftime('%B %d %Y')%></span></div>
<%end%>
<%end%>
<%= link_to 'Add New comment', new_blog_comment_path(@blog)%>
The comments section should be changed when adding a new comment, and we are caching them with the key “blog_comments_#{@blog.id}”). Here is what the log says:
Write fragment views/blog_comments_1
Expire Cache
To expire this cache, call the expire_fragment method . Here, it is called when a new comment is created:class BlogsController < ApplicationController
def create
@blog = Blog.find params[:blog_id]
expire_fragment("blog_comments_#{@blog.id}")
@comment = @blog.comments.new(params[:comment])
end
end
Configuration
If a key name is not provided to the cache function, then Rails will cache the block based on the name of the controller action. In this case, you would expire the cache by calling expire_fragment(:controller => ‘blogs’, :action => ‘show’). This, obviously, will generate conflicts if- We also caching action
- And having multiple fragment cache per action
cache(:action => 'show', :controller => 'blogs', :action_suffix => 'blog_comments' )
To expire this cache:
expire_fragment(:controller => 'blogs', :action => 'show',:action_suffix => 'blog_comments')
Sweepers
Calling an expire method inside a controller action is a bad idea. Rails provides another mechanism for this expiration process: Sweepers. Sweepers are responsible expiring caches . They observe models and execute callbacks as defined in the sweeper. For example I created a blog_sweeper.rb file:class BlogSweeper < ActionController::Caching::Sweeper
observe Blog
def after_update(blog)
expire_action(:controller => :blogs, :action => :index)
end
def after_create(blog)
expire_action(:controller => :blogs, :action => :index)
end
end
But we need to call this sweeper in our controller for this call cache_sweeper method in controller
class BlogController < ApplicationController
cache_sweeper :blog_sweeper, :only => [:index]
end
Cache Digests
Cache digests are a better way to handle fragment caching. It’s based on a Russian Doll scheme, meaning, when you have nested cached fragments and the nested content changes, only expire the cache for that content reusing the rest of the cache. Here is an example from our same blog application. Our model structure look like :class User < ActiveRecord::Base
has_many :blogs
end
# Blog model
class Blog < ActiveRecord::Base
has_many :comments
belongs_to :user, :touch => true
end
#comment model
class Comment < ActiveRecord::Base
belongs_to :blog, :touch => true
end
We are using the touch option here so if we update a comment, the blog will get a new updated_at value.
Here is the ‘users/show.html.erb’, showing user’s detail with their blog and comments:
<%cache @user do%>
<div> Name:<%= @user.name %></div>
<div> Email:<%= @user.email %></div>
<%= render @user.blogs%>
<%end%>
<!-- 'blogs/blog.html.erb' -->
<%cache blog do%>
<div><%= blog.title %></div>
<div><%= blog.desc %></div>
<%= render blog.comments%>
<%end%>
<!-- 'comments/comment.html.erb' -->
<%cache comment do%>
<%= comment.value %>
<%end%>
You can see that we cache the comments inside the blog and the blog inside the user. In an example where we have a single user with 2 blog posts and 1 comment, the cache keys look like:
views/users/1-20130118063610
views/blogs/1-20130118063600
views/comments/1-20130118063600
views/blogs/2-20130118063610
It is a combination of view path, object id, and timestamp of the last time it was updated. This combination ensures a cache will always be expired if a model is updated. If we add a new blog or edit a blog it will expire the cache of user along with blog cache. because we have used touch option in our blog model.
Likewise, if we add or edit a comment, everything expires. However, if we change any thing in the view itself, such as a new style, reloading the page will not reflect the change as our cache keys are not expired. We can mitigate this by keeping a version of the cache keys and changing that version as needed.
<%cache ['v1', @user] do%>
<div>Name:<%= @user.name %></div>
<div>Email:<%= @user.email %></div>
<%= render @user.blogs%>
<%end%>
<!-- 'blogs/blog.html.erb' -->
<%cache ['v2',blog] do%>
<div><%= blog.title%></div>
<div><%= blog.desc %></div>
<%= render blog.comments%>
<%end%>
<!-- 'comments/comment.html.erb' -->
<%cache ['v3',comment] do%>
<%= comment.value %>
<%end%>
Every time we modify our view, we need to update the version of the cache key. Now we have more problems:
- We need to remember the version number of the template
- Parent template versions should be changed if child template is modified.
- If the same partial is used in different views, then it hard to maintain versions
<%cache @user do%>
Name:<%= @user.name %>
Email:<%= @user.email %>
<%= render @user.blogs%>
<%end%>
<!-- 'blogs/blog.html.erb' -->
<%cache blog do%>
<%= blog.title %>
<%= blog.desc %>
<%= render blog.comments%>
<%end%>
<!-- 'comments/comment.html.erb' -->
<%cache comment do%>
<%= comment.value %>
<%end%>
Rendering the user’s show it will generate a log like:
![cache_digest1](https://uploads.sitepoint.com/wp-content/uploads/2013/01/cache_digest1-530x258.png)
Check Dependencies of Template
To check the dependencies of a view, the cache_digests gem provides two rake tasks 1. rake cache_digests:dependencies TEMPLATE=users/show I got the following output by running the above task![dependency_task](https://uploads.sitepoint.com/wp-content/uploads/2013/01/dependency_task.png)
![nested_dependencies_task](https://uploads.sitepoint.com/wp-content/uploads/2013/01/nested_dependencies_task.png)
<%= render @user.last_one_month_blogs%>
So we need to mention partial name and collection here
<%= render :partial => 'blogs/_blogs', :collection => @user.last_one_month_blogs%>
2. If we are using a helper in our render call. Then we need to use a comment that will specify the template dependency
<%# Template Dependency: comments/comment %>
<%= render_comments(blog)%>
Wrap Up
Basically, I wanted to explain caching in Rails and how many ways caching can be accomplished. More Resources -:- http://railscasts.com/episodes/90-fragment-caching-revised
- http://guides.rubyonrails.org/caching_with_rails.html
- http://railscasts.com/episodes/387-cache-digests
- http://blog.remarkablelabs.com/2012/12/russian-doll-caching-cache-digests-rails-4-countdown-to-2013
Frequently Asked Questions (FAQs) about Caching and Cache Digest
What is the main purpose of caching in web development?
Caching in web development is primarily used to store copies of files or data in a cache (temporary storage) so that they can be accessed more quickly. When a user visits a website, the browser downloads all the necessary files to display the site correctly. If the user visits the same site again, the browser will not need to download the same files again, instead, it will retrieve them from the cache, which speeds up the loading time. This not only enhances the user experience but also reduces the load on the server.
How does Cache Digest work?
Cache Digest is a mechanism that allows a client (usually a browser) to inform a server about the contents of its cache. This is done using a digest, which is a compact representation of the cache’s content. The server can then use this information to avoid sending resources that the client already has, thereby reducing bandwidth usage and improving page load times.
What is the difference between Cache Digest and Russian Doll Caching?
While both Cache Digest and Russian Doll Caching are caching techniques, they serve different purposes and work in different ways. Cache Digest is a method used by a client to inform a server about the contents of its cache, allowing the server to avoid sending resources that the client already has. On the other hand, Russian Doll Caching is a technique used in Rails applications where one cache is nested inside another. This allows for efficient invalidation of caches, as changing a single element can invalidate all caches that contain it.
How can I implement Cache Digest in my web application?
Implementing Cache Digest in a web application involves several steps. First, the client needs to generate a digest of its cache’s contents. This can be done using a variety of algorithms, such as Gossip or Cuckoo. The client then sends this digest to the server with each request. The server uses this digest to determine which resources the client already has and avoids sending those resources.
What are the benefits of using Cache Digest?
Using Cache Digest can bring several benefits. First, it can significantly reduce bandwidth usage, as the server avoids sending resources that the client already has. This can lead to faster page load times and a better user experience. Additionally, it can reduce the load on the server, as it does not need to send as much data.
Are there any drawbacks to using Cache Digest?
While Cache Digest can bring significant benefits, it also has some potential drawbacks. One of the main ones is the complexity of implementation. Generating and managing cache digests can be complex, especially for large applications. Additionally, if not managed correctly, cache digests can become out of sync with the actual contents of the cache, leading to inefficiencies.
How does Cache Digest compare to other caching techniques?
Cache Digest offers some unique advantages over other caching techniques. Unlike traditional caching, which only stores resources for a limited time, Cache Digest allows the client to inform the server about the contents of its cache. This can lead to more efficient use of bandwidth and faster page load times. However, it also requires more complex implementation and management.
Can Cache Digest be used with other caching techniques?
Yes, Cache Digest can be used in conjunction with other caching techniques. For example, it can be used alongside traditional caching to further optimize resource delivery. Additionally, it can be used with techniques like Russian Doll Caching to provide efficient cache invalidation.
What factors should I consider when deciding to use Cache Digest?
When deciding to use Cache Digest, there are several factors to consider. These include the complexity of your application, the resources available for implementation and management, and the potential benefits in terms of bandwidth usage and page load times. It’s also important to consider the potential drawbacks, such as the possibility of cache digests becoming out of sync.
Where can I find more information about Cache Digest?
There are many resources available online for learning more about Cache Digest. These include technical documentation, blog posts, and tutorials. Additionally, many web development communities and forums have discussions and threads dedicated to caching techniques, including Cache Digest.
Web Developer! Rashmi usually write blog at http://raysrashmi.com.