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,
def index
@posts = Post.all
fresh_when(etag: @posts, last_modified: @posts.maximum(:updated_at))
end
or simply written as,
def index
@posts = Post.all
fresh_when(@posts)
end
This works with stale?
method too, we can pass a collection of records to it.
For example,
def index
@posts = Post.all
if stale?(@posts)
render json: @posts
end
end
To see this in action, let's begin by making a request at /posts
.
$ curl -I http://localhost:3000/posts
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
ETag: W/"a2b68b7a7f8c67f1b88848651a86f5f5"
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 7c8457e7-9d26-4646-afdf-5eb44711fa7b
X-Runtime: 0.074238
In the second request, we would send the ETag in If-None-Match
header to check
if the data has changed.
$ curl -I -H 'If-None-Match: W/"a2b68b7a7f8c67f1b88848651a86f5f5"' http://localhost:3000/posts
HTTP/1.1 304 Not Modified
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
ETag: W/"a2b68b7a7f8c67f1b88848651a86f5f5"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 6367b2a5-ecc9-4671-8a79-34222dc50e7f
X-Runtime: 0.003756
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,
Started GET "/posts" for ::1 at 2016-08-06 00:39:44 +0530
Processing by PostsController#index as HTML
(0.2ms) SELECT MAX("posts"."updated_at") FROM "posts"
(0.1ms) SELECT COUNT(*) AS "size", MAX("posts"."updated_at") AS timestamp FROM "posts"
Rendering posts/index.html.erb within layouts/application
Post Load (0.2ms) SELECT "posts".* FROM "posts"
Rendered posts/index.html.erb within layouts/application (2.0ms)
Completed 200 OK in 31ms (Views: 27.1ms | ActiveRecord: 0.5ms)
Started GET "/posts" for ::1 at 2016-08-06 00:39:46 +0530
Processing by PostsController#index as HTML
(0.2ms) SELECT MAX("posts"."updated_at") FROM "posts"
(0.1ms) SELECT COUNT(*) AS "size", MAX("posts"."updated_at") AS timestamp FROM "posts"
Completed 304 Not Modified in 2ms (ActiveRecord: 0.3ms)
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.
If this blog was helpful, check out our full blog archive.