Back to Blog

Rails 5 Passing records to fresh_when and stale?

on August 16, 2016
This blog is part of our Rails 5 series.

Rails has powerful tools to control caching of resources via HTTP such as fresh_when and stale?.

Previously we could only pass a single record to these methods but now Rails 5 adds support for accepting a collection of records as well. For example,

1
2def index
3  @posts = Post.all
4  fresh_when(etag: @posts, last_modified: @posts.maximum(:updated_at))
5end
6

or simply written as,

1
2def index
3  @posts = Post.all
4  fresh_when(@posts)
5end
6

This works with stale? method too, we can pass a collection of records to it. For example,

1
2def index
3  @posts = Post.all
4
5  if stale?(@posts)
6    render json: @posts
7  end
8end
9

To see this in action, let's begin by making a request at /posts.

1
2$ curl -I http://localhost:3000/posts
3
4HTTP/1.1 200 OK
5X-Frame-Options: SAMEORIGIN
6X-XSS-Protection: 1; mode=block
7X-Content-Type-Options: nosniff
8ETag: W/"a2b68b7a7f8c67f1b88848651a86f5f5"
9Content-Type: text/html; charset=utf-8
10Cache-Control: max-age=0, private, must-revalidate
11X-Request-Id: 7c8457e7-9d26-4646-afdf-5eb44711fa7b
12X-Runtime: 0.074238
13

In the second request, we would send the ETag in If-None-Match header to check if the data has changed.

1
2$ curl -I -H 'If-None-Match: W/"a2b68b7a7f8c67f1b88848651a86f5f5"' http://localhost:3000/posts
3
4HTTP/1.1 304 Not Modified
5X-Frame-Options: SAMEORIGIN
6X-XSS-Protection: 1; mode=block
7X-Content-Type-Options: nosniff
8ETag: W/"a2b68b7a7f8c67f1b88848651a86f5f5"
9Cache-Control: max-age=0, private, must-revalidate
10X-Request-Id: 6367b2a5-ecc9-4671-8a79-34222dc50e7f
11X-Runtime: 0.003756
12

Since there's no change, the server returned HTTP/1.1 304 Not Modified. If these requests were made from a browser, it would automatically use the version in its cache on the second request.

The second request was obviously faster as the server was able to save the time of fetching data and rendering it. This can be seen in Rails log,

1
2Started GET "/posts" for ::1 at 2016-08-06 00:39:44 +0530
3Processing by PostsController#index as HTML
4   (0.2ms)  SELECT MAX("posts"."updated_at") FROM "posts"
5   (0.1ms)  SELECT COUNT(*) AS "size", MAX("posts"."updated_at") AS timestamp FROM "posts"
6  Rendering posts/index.html.erb within layouts/application
7  Post Load (0.2ms)  SELECT "posts".* FROM "posts"
8  Rendered posts/index.html.erb within layouts/application (2.0ms)
9Completed 200 OK in 31ms (Views: 27.1ms | ActiveRecord: 0.5ms)
10
11
12Started GET "/posts" for ::1 at 2016-08-06 00:39:46 +0530
13Processing by PostsController#index as HTML
14   (0.2ms)  SELECT MAX("posts"."updated_at") FROM "posts"
15   (0.1ms)  SELECT COUNT(*) AS "size", MAX("posts"."updated_at") AS timestamp FROM "posts"
16Completed 304 Not Modified in 2ms (ActiveRecord: 0.3ms)
17

Cache expires when collection of records is updated. For example, an addition of a new record to the collection or a change in any of the records (which changes updated_at) would change the ETag.

Now that Rails 5 supports collection of records in fresh_when and stale?, we have an improved system to cache resources and make our applications faster. This is more helpful when we have controller actions with time consuming data processing logic.


You might also like

If you liked this blog post, check out similar ones from BigBinary