Method: ActionController::ConditionalGet#fresh_when

Defined in:
actionpack/lib/action_controller/metal/conditional_get.rb

#fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil) ⇒ Object

Sets the ‘etag`, `last_modified`, or both on the response, and renders a `304 Not Modified` response if the request is already fresh.

#### Options

‘:etag` : Sets a “weak” ETag validator on the response. See the `:weak_etag` option.

‘:weak_etag` : Sets a “weak” ETag validator on the response. Requests that specify an

`If-None-Match` header may receive a `304 Not Modified` response if the
ETag matches exactly.

: A weak ETag indicates semantic equivalence, not byte-for-byte equality, so

they're good for caching HTML pages in browser caches. They can't be used
for responses that must be byte-identical, like serving `Range` requests
within a PDF file.

‘:strong_etag` : Sets a “strong” ETag validator on the response. Requests that specify an

`If-None-Match` header may receive a `304 Not Modified` response if the
ETag matches exactly.

: A strong ETag implies exact equality – the response must match byte for

byte. This is necessary for serving `Range` requests within a large video
or PDF file, for example, or for compatibility with some CDNs that don't
support weak ETags.

‘:last_modified` : Sets a “weak” last-update validator on the response. Subsequent requests

that specify an `If-Modified-Since` header may receive a `304 Not
Modified` response if `last_modified` <= `If-Modified-Since`.

‘:public` : By default the `Cache-Control` header is private. Set this option to

`true` if you want your application to be cacheable by other devices, such
as proxy caches.

‘:cache_control` : When given, will overwrite an existing `Cache-Control` header. For a list

of `Cache-Control` directives, see the [article on
MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).

‘:template` : By default, the template digest for the current controller/action is

included in ETags. If the action renders a different template, you can
include its digest instead. If the action doesn't render a template at
all, you can pass `template: false` to skip any attempt to check for a
template digest.

#### Examples

def show
  @article = Article.find(params[:id])
  fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
end

This will send a ‘304 Not Modified` response if the request specifies a matching ETag and `If-Modified-Since` header. Otherwise, it will render the `show` template.

You can also just pass a record:

def show
  @article = Article.find(params[:id])
  fresh_when(@article)
end

‘etag` will be set to the record, and `last_modified` will be set to the record’s ‘updated_at`.

You can also pass an object that responds to ‘maximum`, such as a collection of records:

def index
  @articles = Article.all
  fresh_when(@articles)
end

In this case, ‘etag` will be set to the collection, and `last_modified` will be set to `maximum(:updated_at)` (the timestamp of the most recently updated record).

When passing a record or a collection, you can still specify other options, such as ‘:public` and `:cache_control`:

def show
  @article = Article.find(params[:id])
  fresh_when(@article, public: true, cache_control: { no_cache: true })
end

The above will set ‘Cache-Control: public, no-cache` in the response.

When rendering a different template than the controller/action’s default template, you can indicate which digest to include in the ETag:

before_action { fresh_when @article, template: "widgets/show" }


137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'actionpack/lib/action_controller/metal/conditional_get.rb', line 137

def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
  response.cache_control.delete(:no_store)
  weak_etag ||= etag || object unless strong_etag
  last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at)

  if strong_etag
    response.strong_etag = combine_etags strong_etag,
      last_modified: last_modified, public: public, template: template
  elsif weak_etag || template
    response.weak_etag = combine_etags weak_etag,
      last_modified: last_modified, public: public, template: template
  end

  response.last_modified = last_modified if last_modified
  response.cache_control[:public] = true if public
  response.cache_control.merge!(cache_control)

  head :not_modified if request.fresh?(response)
end