You can use ETags to allow clients to use cached responses, if your application would send the same contents as before.
Besides what "actually" defines your response's contents, your application probably also considers "global" conditions, like which user is signed in:
class ApplicationController < ActionController::Base
etag { current_user&.id }
etag { current_user&.updated_at }
end
Under the hood, Rails generates an ETag header value like W/"f14ce3710a2a3187802cadc7e0c8ea99"
. In doing so, all objects from that etaggers list are serialized and Time objects lose their millisecond resolution.
You likely won't notice for a while (ask me how I know) but occasionally encounter tests which are "randomly failing".
Example
When users change their name and visit a page they visited less than a second ago (yes, that is not an issue with humans), your application will respond with an HTTP 304 and the browser will re-use the response from before which still contained the old username.
To avoid that, convert any Time
objects into a string or number before an ETag is generated from them.
class ApplicationController < ActionController::Base
etag { current_user&.id }
etag { current_user&.updated_at&.to_f }
end
You might even want to patch Rails' etagging logic so you don't have to remember doing that. Here you go:
class ApplicationController < ActionController::Base
etag { current_user&.id }
etag { current_user&.updated_at }
private
def combine_etags(...)
# When using Time objects for etagging, their milliseconds are ignored because of how
# ActiveSupport::Cache#expand_cache_key works (which is called to build ETag contents).
# We convert them to floats to avoid that.
super.map! { |object| object.is_a?(Time) ? object.to_f : object }
end
end