Skip to content

Commit

Permalink
Added controller-level etag additions that will be part of the action…
Browse files Browse the repository at this point in the history
… etag computation *Jeremy Kemper/DHH*
  • Loading branch information
dhh committed Aug 29, 2012
1 parent 502d5e2 commit ed5c938
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
12 changes: 12 additions & 0 deletions actionpack/CHANGELOG.md
@@ -1,5 +1,17 @@
## Rails 4.0.0 (unreleased) ##

* Added controller-level etag additions that will be part of the action etag computation *Jeremy Kemper/DHH*

class InvoicesController < ApplicationController
etag { current_user.try :id }

def show
# Etag will differ even for the same invoice when it's viewed by a different current_user
@invoice = Invoice.find(params[:id])
fresh_when(@invoice)
end
end

* Add automatic template digests to all CacheHelper#cache calls (originally spiked in the cache_digests plugin) *DHH*

* When building a URL fails, add missing keys provided by Journey. Failed URL
Expand Down
38 changes: 34 additions & 4 deletions actionpack/lib/action_controller/metal/conditional_get.rb
Expand Up @@ -5,6 +5,30 @@ module ConditionalGet
include RackDelegation
include Head

included { cattr_accessor(:etaggers) { Array.new } }

module ClassMethods
# Allows you to consider additional controller-wide information when generating an etag.
# For example, if you serve pages tailored depending on who's logged in at the moment, you
# may want to add the current user id to be part of the etag to prevent authorized displaying
# of cached pages.
#
# === Example
#
# class InvoicesController < ApplicationController
# etag { current_user.try :id }
#
# def show
# # Etag will differ even for the same invoice when it's viewed by a different current_user
# @invoice = Invoice.find(params[:id])
# fresh_when(@invoice)
# end
# end
def etag(&etagger)
self.etaggers += [etagger]
end
end

# Sets the etag, last_modified, or both on the response and renders a
# <tt>304 Not Modified</tt> response if the request is already fresh.
#
Expand Down Expand Up @@ -42,12 +66,12 @@ def fresh_when(record_or_options, additional_options = {})
options.assert_valid_keys(:etag, :last_modified, :public)
else
record = record_or_options
options = { :etag => record, :last_modified => record.try(:updated_at) }.merge(additional_options)
options = { etag: record, last_modified: record.try(:updated_at) }.merge(additional_options)
end

response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
response.cache_control[:public] = true if options[:public]
response.etag = combine_etags(options[:etag]) if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
response.cache_control[:public] = true if options[:public]

head :not_modified if request.fresh?(response)
end
Expand Down Expand Up @@ -133,5 +157,11 @@ def expires_in(seconds, options = {}) #:doc:
def expires_now #:doc:
response.cache_control.replace(:no_cache => true)
end


private
def combine_etags(etag)
[ etag, *etaggers.map { |etagger| instance_exec &etagger }.compact ]
end
end
end
32 changes: 32 additions & 0 deletions actionpack/test/controller/render_test.rb
Expand Up @@ -22,6 +22,18 @@ def new
end
end

class TestControllerWithExtraEtags < ActionController::Base
etag { nil }
etag { 'ab' }
etag { :cde }
etag { [:f] }
etag { nil }

def fresh
render text: "stale" if stale?(etag: '123')
end
end

class TestController < ActionController::Base
protect_from_forgery

Expand Down Expand Up @@ -1626,6 +1638,26 @@ def test_last_modified_works_with_less_than_too
end
end

class EtagRenderTest < ActionController::TestCase
tests TestControllerWithExtraEtags

def setup
super
@request.host = "www.nextangle.com"
end

def test_multiple_etags
@request.if_none_match = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key([ "123", 'ab', :cde, [:f] ]))}")
get :fresh
assert_response :not_modified

@request.if_none_match = %("nomatch")
get :fresh
assert_response :success
end
end


class MetalRenderTest < ActionController::TestCase
tests MetalTestController

Expand Down

0 comments on commit ed5c938

Please sign in to comment.