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.