Skip to content

Commit ed5c938

Browse files
author
David Heinemeier Hansson
committed
Added controller-level etag additions that will be part of the action etag computation *Jeremy Kemper/DHH*
1 parent 502d5e2 commit ed5c938

3 files changed

Lines changed: 78 additions & 4 deletions

File tree

actionpack/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
## Rails 4.0.0 (unreleased) ##
22

3+
* Added controller-level etag additions that will be part of the action etag computation *Jeremy Kemper/DHH*
4+
5+
class InvoicesController < ApplicationController
6+
etag { current_user.try :id }
7+
8+
def show
9+
# Etag will differ even for the same invoice when it's viewed by a different current_user
10+
@invoice = Invoice.find(params[:id])
11+
fresh_when(@invoice)
12+
end
13+
end
14+
315
* Add automatic template digests to all CacheHelper#cache calls (originally spiked in the cache_digests plugin) *DHH*
416

517
* When building a URL fails, add missing keys provided by Journey. Failed URL

actionpack/lib/action_controller/metal/conditional_get.rb

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@ module ConditionalGet
55
include RackDelegation
66
include Head
77

8+
included { cattr_accessor(:etaggers) { Array.new } }
9+
10+
module ClassMethods
11+
# Allows you to consider additional controller-wide information when generating an etag.
12+
# For example, if you serve pages tailored depending on who's logged in at the moment, you
13+
# may want to add the current user id to be part of the etag to prevent authorized displaying
14+
# of cached pages.
15+
#
16+
# === Example
17+
#
18+
# class InvoicesController < ApplicationController
19+
# etag { current_user.try :id }
20+
#
21+
# def show
22+
# # Etag will differ even for the same invoice when it's viewed by a different current_user
23+
# @invoice = Invoice.find(params[:id])
24+
# fresh_when(@invoice)
25+
# end
26+
# end
27+
def etag(&etagger)
28+
self.etaggers += [etagger]
29+
end
30+
end
31+
832
# Sets the etag, last_modified, or both on the response and renders a
933
# <tt>304 Not Modified</tt> response if the request is already fresh.
1034
#
@@ -42,12 +66,12 @@ def fresh_when(record_or_options, additional_options = {})
4266
options.assert_valid_keys(:etag, :last_modified, :public)
4367
else
4468
record = record_or_options
45-
options = { :etag => record, :last_modified => record.try(:updated_at) }.merge(additional_options)
69+
options = { etag: record, last_modified: record.try(:updated_at) }.merge(additional_options)
4670
end
4771

48-
response.etag = options[:etag] if options[:etag]
49-
response.last_modified = options[:last_modified] if options[:last_modified]
50-
response.cache_control[:public] = true if options[:public]
72+
response.etag = combine_etags(options[:etag]) if options[:etag]
73+
response.last_modified = options[:last_modified] if options[:last_modified]
74+
response.cache_control[:public] = true if options[:public]
5175

5276
head :not_modified if request.fresh?(response)
5377
end
@@ -133,5 +157,11 @@ def expires_in(seconds, options = {}) #:doc:
133157
def expires_now #:doc:
134158
response.cache_control.replace(:no_cache => true)
135159
end
160+
161+
162+
private
163+
def combine_etags(etag)
164+
[ etag, *etaggers.map { |etagger| instance_exec &etagger }.compact ]
165+
end
136166
end
137167
end

actionpack/test/controller/render_test.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ def new
2222
end
2323
end
2424

25+
class TestControllerWithExtraEtags < ActionController::Base
26+
etag { nil }
27+
etag { 'ab' }
28+
etag { :cde }
29+
etag { [:f] }
30+
etag { nil }
31+
32+
def fresh
33+
render text: "stale" if stale?(etag: '123')
34+
end
35+
end
36+
2537
class TestController < ActionController::Base
2638
protect_from_forgery
2739

@@ -1626,6 +1638,26 @@ def test_last_modified_works_with_less_than_too
16261638
end
16271639
end
16281640

1641+
class EtagRenderTest < ActionController::TestCase
1642+
tests TestControllerWithExtraEtags
1643+
1644+
def setup
1645+
super
1646+
@request.host = "www.nextangle.com"
1647+
end
1648+
1649+
def test_multiple_etags
1650+
@request.if_none_match = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key([ "123", 'ab', :cde, [:f] ]))}")
1651+
get :fresh
1652+
assert_response :not_modified
1653+
1654+
@request.if_none_match = %("nomatch")
1655+
get :fresh
1656+
assert_response :success
1657+
end
1658+
end
1659+
1660+
16291661
class MetalRenderTest < ActionController::TestCase
16301662
tests MetalTestController
16311663

0 commit comments

Comments
 (0)