Entity tags (ETags) are a mechanism that web/application servers and browsers use to determine whether the entity or component (images, scripts, stylesheets, page content etc) in the browser’s cache matches the one on the origin server.
Rails 3 and Rails 4 uses ETags by default and let’s look at how they work first:
Lets say you are going to a blog website and requesting a list of posts:
- First Request:
- Browser makes the initial first request
- First Response:
- Rails creates response body
- Rails creates ETag
- Rails responds with ETag header, status code 200
The browser caches the response now. When the browser makes subsequent request, here is the flow:
- Subsequent Request:
- Browser makes request with header ‘If-None-Matched’ with ETag value from the initial request.
- Subsequent Response:
- Rails creates body and creates ETag
- Compares ETag value with ‘If-None-Matched’ value
- If ETag match then response body is not included in response and returns with 304 Not Modified status
- If ETag doesn’t match then body is included in the response and ETags are included in header
So what’s the advantage when ETag matches with the one on the server? The Rails doesn’t send the body content as part of the response. So the response is small and fast to travel on the network. The browser loads the contents from the browser cache instead which makes website faster.
So what you have to do to enable ETag chache? Nothing. You don’t have to do anything to take advantage of ETag cache.
The below code uses Rails default ETag cache:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
So how do rails generate ETags? Well, it will render the response and generates the ETags from the response body. The code may look like something like this in rails:
It is not a good idea to process the entire response body everytime to generate ETag. Generating entire response body involves calling the DB to fetch all the related data and processing html templates and partial page renderings. This is a costly process. How do you solve this issue? You have fresh_when and stale? rails cache helper come in handy to rescue you.
The code will look like this with fresh_when and stale?:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
So how do rails generate ETags now? The code may look like something like this in rails for stale? and fresh_when helpers:
Cache_key is combination of
model_name/model.id-model.updated_at. It will be like this for Post model:
When do you use fresh_when and stale? cache helper and what’s difference?
If you have special response processing like the one in show method, then use stale? helper. If you don’t have any special response processing like the one in edit method and using default-rendering mechanism (i.e. you’re not using respond_to or calling render yourself) then use fresh_when.
Customize ETag generation
If you cache based on
current_user or current_customer, you can pass multiple arguments to generate ETag. The arguments have to be a Hash.
The sample code will look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
When look at edit and recent methods, there are some repetitions. How do we DRY it up? That’s when Rails 4 declarative ETags comes into play.
Rails 3 and Rails 4 uses ETags by default for browser caching. But Rails 4 has a feature called declarative ETags, which allows you to add additional controller-wide information when generating an ETag.
Let’s look at how to apply Rails 4 declarative ETags to the above code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
You can have multiple declarative ETags like this:
1 2 3 4 5 6 7
You can have conditions inside declarative ETags like this one to run them only on certain actions:
1 2 3 4 5
Declarative ETags doesn’t support :only, :if options yet like one we have on before_filter/before_action or after_filer/after_action to run call back only on certain actions.