`fresh_when` usage for dynamic queries
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:
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