Skip to main content

`fresh_when` usage for dynamic queries

Rails • Asked by Nicolas Brousse


I'm recently looking to use fresh_when method on my PostController.

class PostsController < ApplicationController
  def index
    @posts = Post.published.order(published_at: :desc)
    fresh_when @posts

  def show
    @post = Post.published.find(params[:id])
    fresh_when @post
class Post < ApplicationRecord
  scope :published, -> { where("published_at IS NOT NULL AND published_at <= ?", }
  scope :unpublished, -> { where("published_at IS NULL OR published_at > ?", }

All is good for the show method, ETag and Last-Modified headers looks good.

But it's not the case for the index method. Each request the ETag is not the same.

I took a look at Rails code and I discover that the ETag generation is done by generate_strong_etag who call a bit later retrieve_cache_key.
In index action @posts return an ActiveRecord::Relation object who reponds to cache_key method. And this method return a key based on the sql request.
So because of the usage, all requests are different ...

I resolve the problem by doing this:

fresh_when @posts, etag: @posts.to_a

But I'm not sure if it is the best way to do this.
That's why I asking you here :)

Hi Nicolas,

What version of Rails are you using? It appears that in Rails 5 you can now start using fresh_when or stale? for collections, see:

Hi Jacob,

I'm using Rails 5.0.2. Yes, I saw in their doc that they speak about using fresh_when for collections:

With the example of your link, it works because Post.all will always generate the same query. And by checking how the Rails code works, I saw they call cache_key method who generated a string key in function of the SQL query.

In my case, because of, the SQL query is always different. So, the ETag too...

That's why I don't know if it is a problem of usage or a problem onto Rails code.

OH! I'm tracking now - one way you could find out for sure is to set a fixed time on your scopes. So instead of using, just use a fixed time and see if you're getting the proper cached response or a fresh query. If the same, then you know that your use of is causing a new key to be generated.

I tried and yes if I use a static Time the query stay the so ETag too. That's how I discovered why my ETag was always different.

But anyway if I have a query without something variable like my it mean that if I update a Post the ETag will stay the same. And that's not logic.

The etag wouldn't stay the same. If you remove and modify one of the records, it will not return a 304 Not Modified status which is signifying that a record has changed and so you need to refresh the page.

I'm not sure what your intended use is, but I think a little bit of restructuring would give you the results you want. If you were to add a new boolean for published, you could let that be your query instead. So your published scope could be published: true instead of published_at <=

Outside of that, I'm not sure what kind of edge cases may arise by using the array as your etag - it may be a perfectly viable solution, I'm jut not sure though.

@Nicolas Brousse I think your solution it correct, that's how I do it, too. You even don't need to pass the record, if you pass the etag option. I would write it like so:

fresh_when etag: @posts.to_a

Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 22,346+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.