Ask A Question

Notifications

You’re not receiving notifications from this thread.

`fresh_when` usage for dynamic queries

Nicolas Brousse asked in Rails

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 :)

Reply

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

Reply

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.

Reply

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.

Reply

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.

Reply

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.

Reply

@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
Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 86,946+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.