Skip to main content

`fresh_when` usage for dynamic queries

Rails • Asked by Nicolas Brousse

Hey!

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
  end

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

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 Time.now 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:

http://blog.bigbinary.com/2016/08/16/rails-5-supports-passing-collection-of-records-to-fresh_when-and-stale.html


Hi Jacob,

I'm using Rails 5.0.2. Yes, I saw in their doc that they speak about using fresh_when for collections: http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-fresh_when.

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 Time.now, 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 Time.now, 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 Time.now 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 Time.now 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 Time.now 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 <= Time.now

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.