Fragment Caching in Rails 5

In development.rb, turn on caching in development environment:

config.action_controller.perform_caching = true

Encose the article show page with cache block.

<% cache @article do %>
  <h1><%= @article.title %></h1>
    <p class="author"><em>from <%=h @article.author %></em></p>

    <%=simple_format @article.content %>

    <p><%= link_to "Edit", edit_article_path(@article) %> | <%= link_to "Back to Articles", articles_path %></p>

    <% unless @article.comments.empty? %>
      <h2><%= pluralize(@article.comments.size, 'comment') %></h2>

      <div id="comments">
      <% for comment in @article.comments %>
        <div class="comment">
          <strong><%= link_to_unless comment.url.blank?, h(comment.author), h(comment.url) %></strong>
          <em>on <%= comment.created_at.strftime('%b %d, %Y at %H:%M') %></em>
          <%=simple_format comment.content %>
        </div>
      <% end %>
      </div>
    <% end %>
<% end %>

<h3>Add your comment:</h3>
<%= render :partial => 'comments/form' %>

The first time load of the article show page takes 286 ms.

Started GET "/articles/1" for ::1 at 2016-05-05 16:48:26 -0700
  ActiveRecord::SchemaMigration Load (0.1ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by ArticlesController#show as HTML
  Parameters: {"id"=>"1"}
  Article Load (0.1ms)  SELECT  "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Rendering articles/show.html.erb within layouts/application
Read fragment views/articles/1-20160505234454273613/1aa0a9a64322982c2c37d104042d43fc (0.1ms)
  Comment Exists (0.2ms)  SELECT  1 AS one FROM "comments" WHERE "comments"."article_id" = ? LIMIT ?  [["article_id", 1], ["LIMIT", 1]]
   (0.1ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
Write fragment views/articles/1-20160505234454273613/1aa0a9a64322982c2c37d104042d43fc (0.1ms)
  Rendered comments/_form.html.erb (15.4ms)
  Rendered articles/show.html.erb within layouts/application (52.4ms)
Completed 200 OK in 286ms (Views: 249.3ms | ActiveRecord: 1.3ms)

Second time loading of that page takes only 30 ms. You can see it in development.log file.

Started GET "/articles/1" for ::1 at 2016-05-05 16:48:56 -0700
Processing by ArticlesController#show as HTML
  Parameters: {"id"=>"1"}
  Article Load (0.3ms)  SELECT  "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Rendering articles/show.html.erb within layouts/application
Read fragment views/articles/1-20160505234454273613/1aa0a9a64322982c2c37d104042d43fc (0.0ms)
  Comment Exists (0.1ms)  SELECT  1 AS one FROM "comments" WHERE "comments"."article_id" = ? LIMIT ?  [["article_id", 1], ["LIMIT", 1]]
   (0.1ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
Write fragment views/articles/1-20160505234454273613/1aa0a9a64322982c2c37d104042d43fc (0.0ms)
  Rendered comments/_form.html.erb (1.3ms)
  Rendered articles/show.html.erb within layouts/application (10.2ms)
Completed 200 OK in 30ms (Views: 27.2ms | ActiveRecord: 0.6ms)

Rails generates the cache key for a given article and knows the corresponding view. We can experiment in the rails console to see the cache_key for an article.

a = Article.first
  Article Load (0.1ms)  SELECT  "articles".* FROM "articles" ORDER BY "articles"."id" ASC LIMIT ?  [["LIMIT", 1]]
 => #<Article id: 1, title: "Fragment Caching", author: "Cinco", content: "Life would be perfect if we could get away with ca...", created_at: "2016-05-05 23:44:54", updated_at: "2016-05-05 23:44:54">
> a.cache_key
 => "articles/1-20160505234454273613"

Let's edit title for an article. Here is the update to the article record.

Started PATCH "/articles/1" for ::1 at 2016-05-05 16:53:07 -0700
Processing by ArticlesController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"+1JDCPyQ5E0", "article"=>{"title"=>"Fragment Caching in Rails 5", "content"=>"Life would be perfect if we could get away with caching the entire contents of a page.", "author"=>"Cinco"}, "commit"=>"Submit", "id"=>"1"}
  Article Load (0.2ms)  SELECT  "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
   (0.0ms)  begin transaction
  SQL (0.3ms)  UPDATE "articles" SET "title" = ?, "updated_at" = ? WHERE "articles"."id" = ?  [["title", "Fragment Caching in Rails 5"], ["updated_at", 2016-05-05 23:53:07 UTC], ["id", 1]]
   (0.5ms)  commit transaction
Redirected to http://localhost:3000/articles/1
Completed 302 Found in 28ms (ActiveRecord: 1.0ms)

If you now go to the article show page, you can see the updated title of the article. It takes only 38 ms to render the article show page. This time there is a new fragment that is written to the file system. The fragment name ends in ca6be0. The older fragment had a name that ends in 42d43fc.

Started GET "/articles/1" for ::1 at 2016-05-05 16:53:07 -0700
Processing by ArticlesController#show as HTML
  Parameters: {"id"=>"1"}
  Article Load (0.1ms)  SELECT  "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Rendering articles/show.html.erb within layouts/application
Read fragment views/articles/1-20160505235307610571/88bf095aecea45718f42d81891ca6be0 (0.0ms)
  Comment Exists (0.1ms)  SELECT  1 AS one FROM "comments" WHERE "comments"."article_id" = ? LIMIT ?  [["article_id", 1], ["LIMIT", 1]]
   (0.1ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
Write fragment views/articles/1-20160505235307610571/88bf095aecea45718f42d81891ca6be0 (0.0ms)
  Rendered comments/_form.html.erb (1.7ms)
  Rendered articles/show.html.erb within layouts/application (10.9ms)
Completed 200 OK in 38ms (Views: 35.0ms | ActiveRecord: 0.4ms)

So, the cache gets invalidated automatically. The view displays the article title with new name. Add a new comment. The log shows the new article getting saved to the database.

Started POST "/comments" for ::1 at 2016-05-05 16:56:11 -0700
Processing by CommentsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"6cPhR31qw", "comment"=>{"article_id"=>"1", "author"=>"Daffy", "url"=>"", "content"=>"I don't agree."}, "commit"=>"Submit"}
   (0.2ms)  begin transaction
  Article Load (0.1ms)  SELECT  "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (0.3ms)  INSERT INTO "comments" ("author", "url", "content", "article_id", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?)  [["author", "Daffy"], ["url", ""], ["content", "I don't agree."], ["article_id", 1], ["created_at", 2016-05-05 23:56:11 UTC], ["updated_at", 2016-05-05 23:56:11 UTC]]
   (0.5ms)  commit transaction
Redirected to http://localhost:3000/articles/1
Completed 302 Found in 6ms (ActiveRecord: 1.1ms)

If you go to the article show page, the new comment is displayed.

Started GET "/articles/1" for ::1 at 2016-05-05 16:56:11 -0700
Processing by ArticlesController#show as HTML
  Parameters: {"id"=>"1"}
  Article Load (0.3ms)  SELECT  "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Rendering articles/show.html.erb within layouts/application
Read fragment views/articles/1-20160505235458699422/88bf095aecea45718f42d81891ca6be0 (0.0ms)
  Comment Exists (0.1ms)  SELECT  1 AS one FROM "comments" WHERE "comments"."article_id" = ? LIMIT ?  [["article_id", 1], ["LIMIT", 1]]
   (0.1ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ?  [["article_id", 1]]
Write fragment views/articles/1-20160505235458699422/88bf095aecea45718f42d81891ca6be0 (0.0ms)
  Rendered comments/_form.html.erb (1.8ms)
  Rendered articles/show.html.erb within layouts/application (12.7ms)
Completed 200 OK in 39ms (Views: 36.0ms | ActiveRecord: 0.6ms)

There is no need to add:

touch: true 

to

belongs_to :article

in comment model.

Summary

In this article, you learned how to use fragment caching in Rails 5.


Related Articles


Ace the Technical Interview

  • Easily find the gaps in your knowledge
  • Get customized lessons based on where you are
  • Take consistent action everyday
  • Builtin accountability to keep you on track
  • You will solve bigger problems over time
  • Get the job of your dreams

Take the 30 Day Coding Skills Challenge

Gain confidence to attend the interview

No spam ever. Unsubscribe anytime.