To allow
HTTP 304 responses
Show archive.org snapshot
, Rails offers the
fresh_when
Show archive.org snapshot
method for controllers.
The most common way is to pass an ActiveRecord instance or scope, and fresh_when
will set fitting E-Tag
and Last-Modified
headers for you. For scopes, an extra query is sent to the database.
fresh_when @users
If you do not want that magic to happen, e.g. because your scope is expensive to resolve, you can not use fresh_when
like above.
You might come up with some way of knowing the timestamp, like keeping the latest update timestamp in a separate place that is inexpensive to read from, and now just want to pass that timestamp to fresh_when
.
The problem
It might seem simple enough to just say:
updated_at = Rails.cache.fetch('updated_at_of_expensive_scope')
fresh_when last_modified: updated_at
The example above calls fresh_when
without an object, so Rails will only set the Last-Modified
header and not generate an E-Tag.
However, the Last-Modified
header's resolution is
using only seconds
Show archive.org snapshot
. If your timestamp holds milliseconds, they are discarded.
While that is fine for humans, automated tests may fail because of that: If two tests run within the same second frame, your application's response will have the same Last-Modified
header value, even when both tests actually have different data.
While test browsers usually clear all data (cookies, LocalStorage, etc.), at least Chrome seems to keep its HTTP cache and your tests will fail "randomly" (= when they happen within a second).
How to fix it
You can probably start your test browser in incognito mode to fix that.
However, the root cause is that your application claims that two different responses are the same.
The proper solution is to pass your high resolution timestamp as the object to fresh_when
and omit the last_modified
option.
updated_at = Rails.cache.fetch('updated_at_of_expensive_scope')
fresh_when updated_at
Rails will generate an E-Tag
header from that and all will be well.