actionpack-action_caching 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem install bundler
4
+ rvm:
5
+ - 1.9.3
6
+ notifications:
7
+ email: false
8
+ irc:
9
+ on_success: change
10
+ on_failure: always
11
+ channels:
12
+ - "irc.freenode.org#rails-contrib"
13
+ campfire:
14
+ on_success: change
15
+ on_failure: always
16
+ rooms:
17
+ - secure: "WikBuknvGGTx/fNGc4qE+8WK+Glt+H+yZKhHXmavRV2zrN3hC0pTPwuGZhNs\nvkc6N9WKud7un2DtWu1v77BgFhIYjfJTRkmoZ8hoNsoHpe93W/a3s8LU30/l\nzDCKoTrqlHT5hJTmEKpNVqkhfFBPiXRFMgFWALUHiA8Q4Z9BUIc="
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rails', '~> 4.0.0.beta1'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 David Heinemeier Hansson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ actionpack-action_caching
2
+ =========================
3
+
4
+ Action caching for Action Pack (removed from core in Rails 4.0)
5
+
6
+ Installation
7
+ ------------
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'actionpack-action_caching'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install actionpack-action_caching
20
+
21
+ Usage
22
+ -----
23
+
24
+ Action caching is similar to page caching by the fact that the entire
25
+ output of the response is cached, but unlike page caching, every
26
+ request still goes through Action Pack. The key benefit of this is
27
+ that filters run before the cache is served, which allows for
28
+ authentication and other restrictions on whether someone is allowed
29
+ to execute such action.
30
+
31
+ class ListsController < ApplicationController
32
+ before_filter :authenticate, except: :public
33
+
34
+ caches_page :public
35
+ caches_action :index, :show
36
+ end
37
+
38
+ In this example, the `public` action doesn't require authentication
39
+ so it's possible to use the faster page caching. On the other hand
40
+ `index` and `show` require authentication. They can still be cached,
41
+ but we need action caching for them.
42
+
43
+ Action caching uses fragment caching internally and an around
44
+ filter to do the job. The fragment cache is named according to
45
+ the host and path of the request. A page that is accessed at
46
+ `http://david.example.com/lists/show/1` will result in a fragment named
47
+ `david.example.com/lists/show/1`. This allows the cacher to
48
+ differentiate between `david.example.com/lists/` and
49
+ `jamis.example.com/lists/` -- which is a helpful way of assisting
50
+ the subdomain-as-account-key pattern.
51
+
52
+ Different representations of the same resource, e.g.
53
+ `http://david.example.com/lists` and
54
+ `http://david.example.com/lists.xml`
55
+ are treated like separate requests and so are cached separately.
56
+ Keep in mind when expiring an action cache that
57
+ `action: 'lists'` is not the same as
58
+ `action: 'list', format: :xml`.
59
+
60
+ You can modify the default action cache path by passing a
61
+ `:cache_path` option. This will be passed directly to
62
+ `ActionCachePath.new`. This is handy for actions with
63
+ multiple possible routes that should be cached differently. If a
64
+ block is given, it is called with the current controller instance.
65
+
66
+ And you can also use `:if` (or `:unless`) to pass a
67
+ proc that specifies when the action should be cached.
68
+
69
+ As of Rails 3.0, you can also pass `:expires_in` with a time
70
+ interval (in seconds) to schedule expiration of the cached item.
71
+
72
+ The following example depicts some of the points made above:
73
+
74
+ class ListsController < ApplicationController
75
+ before_filter :authenticate, except: :public
76
+
77
+ caches_page :public
78
+
79
+ caches_action :index, if: Proc.new do
80
+ !request.format.json? # cache if is not a JSON request
81
+ end
82
+
83
+ caches_action :show, cache_path: { project: 1 },
84
+ expires_in: 1.hour
85
+
86
+ caches_action :feed, cache_path: Proc.new do
87
+ if params[:user_id]
88
+ user_list_url(params[:user_id, params[:id])
89
+ else
90
+ list_url(params[:id])
91
+ end
92
+ end
93
+ end
94
+
95
+ If you pass `layout: false`, it will only cache your action
96
+ content. That's useful when your layout has dynamic information.
97
+
98
+ Warning: If the format of the request is determined by the Accept HTTP
99
+ header the Content-Type of the cached response could be wrong because
100
+ no information about the MIME type is stored in the cache key. So, if
101
+ you first ask for MIME type M in the Accept header, a cache entry is
102
+ created, and then perform a second request to the same resource asking
103
+ for a different MIME type, you'd get the content cached for M.
104
+
105
+ The `:format` parameter is taken into account though. The safest
106
+ way to cache by MIME type is to pass the format in the route.
107
+
108
+ Contributing
109
+ ------------
110
+
111
+ 1. Fork it.
112
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
113
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
114
+ 4. Push to the branch (`git push origin my-new-feature`).
115
+ 5. Create a new Pull Request.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs = ['test']
7
+ t.pattern = 'test/**/*_test.rb'
8
+ t.ruby_opts = ['-w']
9
+ end
10
+
11
+ task default: :test
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'actionpack-action_caching'
5
+ gem.version = '1.0.0'
6
+ gem.author = 'David Heinemeier Hansson'
7
+ gem.email = 'david@loudthinking.com'
8
+ gem.description = 'Action caching for Action Pack (removed from core in Rails 4.0)'
9
+ gem.summary = 'Action caching for Action Pack (removed from core in Rails 4.0)'
10
+ gem.homepage = 'https://github.com/rails/actionpack-action_caching'
11
+
12
+ gem.files = `git ls-files`.split($/)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.require_paths = ['lib']
16
+
17
+ gem.add_dependency 'actionpack', '>= 4.0.0.beta', '< 5.0'
18
+
19
+ gem.add_development_dependency 'mocha'
20
+ gem.add_development_dependency 'activerecord', '>= 4.0.0.beta', '< 5.0'
21
+ end
@@ -0,0 +1,9 @@
1
+ module ActionController
2
+ module Caching
3
+ eager_autoload do
4
+ autoload :Actions
5
+ end
6
+
7
+ include Actions
8
+ end
9
+ end
@@ -0,0 +1,193 @@
1
+ require 'set'
2
+
3
+ module ActionController
4
+ module Caching
5
+ # Action caching is similar to page caching by the fact that the entire
6
+ # output of the response is cached, but unlike page caching, every
7
+ # request still goes through Action Pack. The key benefit of this is
8
+ # that filters run before the cache is served, which allows for
9
+ # authentication and other restrictions on whether someone is allowed
10
+ # to execute such action.
11
+ #
12
+ # class ListsController < ApplicationController
13
+ # before_filter :authenticate, except: :public
14
+ #
15
+ # caches_page :public
16
+ # caches_action :index, :show
17
+ # end
18
+ #
19
+ # In this example, the +public+ action doesn't require authentication
20
+ # so it's possible to use the faster page caching. On the other hand
21
+ # +index+ and +show+ require authentication. They can still be cached,
22
+ # but we need action caching for them.
23
+ #
24
+ # Action caching uses fragment caching internally and an around
25
+ # filter to do the job. The fragment cache is named according to
26
+ # the host and path of the request. A page that is accessed at
27
+ # <tt>http://david.example.com/lists/show/1</tt> will result in a fragment named
28
+ # <tt>david.example.com/lists/show/1</tt>. This allows the cacher to
29
+ # differentiate between <tt>david.example.com/lists/</tt> and
30
+ # <tt>jamis.example.com/lists/</tt> -- which is a helpful way of assisting
31
+ # the subdomain-as-account-key pattern.
32
+ #
33
+ # Different representations of the same resource, e.g.
34
+ # <tt>http://david.example.com/lists</tt> and
35
+ # <tt>http://david.example.com/lists.xml</tt>
36
+ # are treated like separate requests and so are cached separately.
37
+ # Keep in mind when expiring an action cache that
38
+ # <tt>action: 'lists'</tt> is not the same as
39
+ # <tt>action: 'list', format: :xml</tt>.
40
+ #
41
+ # You can modify the default action cache path by passing a
42
+ # <tt>:cache_path</tt> option. This will be passed directly to
43
+ # <tt>ActionCachePath.new</tt>. This is handy for actions with
44
+ # multiple possible routes that should be cached differently. If a
45
+ # block is given, it is called with the current controller instance.
46
+ #
47
+ # And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
48
+ # proc that specifies when the action should be cached.
49
+ #
50
+ # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time
51
+ # interval (in seconds) to schedule expiration of the cached item.
52
+ #
53
+ # The following example depicts some of the points made above:
54
+ #
55
+ # class ListsController < ApplicationController
56
+ # before_filter :authenticate, except: :public
57
+ #
58
+ # caches_page :public
59
+ #
60
+ # caches_action :index, if: Proc.new do
61
+ # !request.format.json? # cache if is not a JSON request
62
+ # end
63
+ #
64
+ # caches_action :show, cache_path: { project: 1 },
65
+ # expires_in: 1.hour
66
+ #
67
+ # caches_action :feed, cache_path: Proc.new do
68
+ # if params[:user_id]
69
+ # user_list_url(params[:user_id, params[:id])
70
+ # else
71
+ # list_url(params[:id])
72
+ # end
73
+ # end
74
+ # end
75
+ #
76
+ # If you pass <tt>layout: false</tt>, it will only cache your action
77
+ # content. That's useful when your layout has dynamic information.
78
+ #
79
+ # Warning: If the format of the request is determined by the Accept HTTP
80
+ # header the Content-Type of the cached response could be wrong because
81
+ # no information about the MIME type is stored in the cache key. So, if
82
+ # you first ask for MIME type M in the Accept header, a cache entry is
83
+ # created, and then perform a second request to the same resource asking
84
+ # for a different MIME type, you'd get the content cached for M.
85
+ #
86
+ # The <tt>:format</tt> parameter is taken into account though. The safest
87
+ # way to cache by MIME type is to pass the format in the route.
88
+ module Actions
89
+ extend ActiveSupport::Concern
90
+
91
+ module ClassMethods
92
+ # Declares that +actions+ should be cached.
93
+ # See ActionController::Caching::Actions for details.
94
+ def caches_action(*actions)
95
+ return unless cache_configured?
96
+ options = actions.extract_options!
97
+ options[:layout] = true unless options.key?(:layout)
98
+ filter_options = options.extract!(:if, :unless).merge(only: actions)
99
+ cache_options = options.extract!(:layout, :cache_path).merge(store_options: options)
100
+
101
+ around_filter ActionCacheFilter.new(cache_options), filter_options
102
+ end
103
+ end
104
+
105
+ def _save_fragment(name, options)
106
+ content = ''
107
+ response_body.each do |parts|
108
+ content << parts
109
+ end
110
+
111
+ if caching_allowed?
112
+ write_fragment(name, content, options)
113
+ else
114
+ content
115
+ end
116
+ end
117
+
118
+ def caching_allowed?
119
+ (request.get? || request.head?) && response.status == 200
120
+ end
121
+
122
+ protected
123
+ def expire_action(options = {})
124
+ return unless cache_configured?
125
+
126
+ if options.is_a?(Hash) && options[:action].is_a?(Array)
127
+ options[:action].each { |action| expire_action(options.merge(action: action)) }
128
+ else
129
+ expire_fragment(ActionCachePath.new(self, options, false).path)
130
+ end
131
+ end
132
+
133
+ class ActionCacheFilter # :nodoc:
134
+ def initialize(options, &block)
135
+ @cache_path, @store_options, @cache_layout =
136
+ options.values_at(:cache_path, :store_options, :layout)
137
+ end
138
+
139
+ def around(controller)
140
+ cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
141
+
142
+ path_options = if @cache_path.respond_to?(:call)
143
+ controller.instance_exec(controller, &@cache_path)
144
+ else
145
+ @cache_path
146
+ end
147
+
148
+ cache_path = ActionCachePath.new(controller, path_options || {})
149
+
150
+ body = controller.read_fragment(cache_path.path, @store_options)
151
+
152
+ unless body
153
+ controller.action_has_layout = false unless cache_layout
154
+ yield
155
+ controller.action_has_layout = true
156
+ body = controller._save_fragment(cache_path.path, @store_options)
157
+ end
158
+
159
+ body = controller.render_to_string(text: body, layout: true) unless cache_layout
160
+
161
+ controller.response_body = body
162
+ controller.content_type = Mime[cache_path.extension || :html]
163
+ end
164
+ end
165
+
166
+ class ActionCachePath
167
+ attr_reader :path, :extension
168
+
169
+ # If +infer_extension+ is +true+, the cache path extension is looked up from the request's
170
+ # path and format. This is desirable when reading and writing the cache, but not when
171
+ # expiring the cache - +expire_action+ should expire the same files regardless of the
172
+ # request format.
173
+ def initialize(controller, options = {}, infer_extension = true)
174
+ if infer_extension
175
+ @extension = controller.params[:format]
176
+ options.reverse_merge!(format: @extension) if options.is_a?(Hash)
177
+ end
178
+
179
+ path = controller.url_for(options).split('://', 2).last
180
+ @path = normalize!(path)
181
+ end
182
+
183
+ private
184
+ def normalize!(path)
185
+ ext = URI.parser.escape(extension) if extension
186
+ path << 'index' if path[-1] == ?/
187
+ path << ".#{ext}" if extension and !path.split('?', 2).first.ends_with?(".#{ext}")
188
+ URI.parser.unescape(path)
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1 @@
1
+ require 'action_controller/action_caching'
@@ -0,0 +1,40 @@
1
+ require 'bundler/setup'
2
+ require 'minitest/autorun'
3
+ require 'action_controller'
4
+ require 'active_record'
5
+ require 'action_controller/action_caching'
6
+
7
+ FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
8
+
9
+ SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
10
+
11
+ module ActionController
12
+ class Base
13
+ include SharedTestRoutes.url_helpers
14
+
15
+ self.view_paths = FIXTURE_LOAD_PATH
16
+ end
17
+
18
+ class TestCase
19
+ def setup
20
+ @routes = SharedTestRoutes
21
+
22
+ @routes.draw do
23
+ get ':controller(/:action)'
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ module RackTestUtils
30
+ def body_to_string(body)
31
+ if body.respond_to?(:each)
32
+ str = ''
33
+ body.each {|s| str << s }
34
+ str
35
+ else
36
+ body
37
+ end
38
+ end
39
+ extend self
40
+ end
@@ -0,0 +1,510 @@
1
+ require 'abstract_unit'
2
+
3
+ CACHE_DIR = 'test_cache'
4
+ # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
5
+ FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
6
+
7
+ class CachingController < ActionController::Base
8
+ abstract!
9
+
10
+ self.cache_store = :file_store, FILE_STORE_PATH
11
+ end
12
+
13
+ class ActionCachingTestController < CachingController
14
+ rescue_from(Exception) { head 500 }
15
+ rescue_from(ActionController::UnknownFormat) { head :not_acceptable }
16
+ if defined? ActiveRecord
17
+ rescue_from(ActiveRecord::RecordNotFound) { head :not_found }
18
+ end
19
+
20
+ # Eliminate uninitialized ivar warning
21
+ before_filter { @title = nil }
22
+
23
+ caches_action :index, :redirected, :forbidden, if: Proc.new { |c| c.request.format && !c.request.format.json? }, expires_in: 1.hour
24
+ caches_action :show, cache_path: 'http://test.host/custom/show'
25
+ caches_action :edit, cache_path: Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : 'http://test.host/edit' }
26
+ caches_action :with_layout
27
+ caches_action :with_format_and_http_param, cache_path: Proc.new { |c| { key: 'value' } }
28
+ caches_action :layout_false, layout: false
29
+ caches_action :with_layout_proc_param, layout: Proc.new { |c| c.params[:layout] }
30
+ caches_action :record_not_found, :four_oh_four, :simple_runtime_error
31
+ caches_action :streaming
32
+ caches_action :invalid
33
+
34
+ layout 'talk_from_action'
35
+
36
+ def index
37
+ @cache_this = MockTime.now.to_f.to_s
38
+ render text: @cache_this
39
+ end
40
+
41
+ def redirected
42
+ redirect_to action: 'index'
43
+ end
44
+
45
+ def forbidden
46
+ render text: 'Forbidden'
47
+ response.status = '403 Forbidden'
48
+ end
49
+
50
+ def with_layout
51
+ @cache_this = MockTime.now.to_f.to_s
52
+ @title = nil
53
+ render text: @cache_this, layout: true
54
+ end
55
+
56
+ def with_format_and_http_param
57
+ @cache_this = MockTime.now.to_f.to_s
58
+ render text: @cache_this
59
+ end
60
+
61
+ def record_not_found
62
+ raise ActiveRecord::RecordNotFound, 'oops!'
63
+ end
64
+
65
+ def four_oh_four
66
+ render text: "404'd!", status: 404
67
+ end
68
+
69
+ def simple_runtime_error
70
+ raise 'oops!'
71
+ end
72
+
73
+ alias_method :show, :index
74
+ alias_method :edit, :index
75
+ alias_method :destroy, :index
76
+ alias_method :layout_false, :with_layout
77
+ alias_method :with_layout_proc_param, :with_layout
78
+
79
+ def expire
80
+ expire_action controller: 'action_caching_test', action: 'index'
81
+ render nothing: true
82
+ end
83
+
84
+ def expire_xml
85
+ expire_action controller: 'action_caching_test', action: 'index', format: 'xml'
86
+ render nothing: true
87
+ end
88
+
89
+ def expire_with_url_string
90
+ expire_action url_for(controller: 'action_caching_test', action: 'index')
91
+ render nothing: true
92
+ end
93
+
94
+ def streaming
95
+ render text: 'streaming', stream: true
96
+ end
97
+
98
+ def invalid
99
+ @cache_this = MockTime.now.to_f.to_s
100
+
101
+ respond_to do |format|
102
+ format.json{ render json: @cache_this }
103
+ end
104
+ end
105
+ end
106
+
107
+ class MockTime < Time
108
+ # Let Time spicy to assure that Time.now != Time.now
109
+ def to_f
110
+ super+rand
111
+ end
112
+ end
113
+
114
+ class ActionCachingMockController
115
+ attr_accessor :mock_url_for
116
+ attr_accessor :mock_path
117
+
118
+ def initialize
119
+ yield self if block_given?
120
+ end
121
+
122
+ def url_for(*args)
123
+ @mock_url_for
124
+ end
125
+
126
+ def params
127
+ request.parameters
128
+ end
129
+
130
+ def request
131
+ Object.new.instance_eval(<<-EVAL)
132
+ def path; '#{@mock_path}' end
133
+ def format; 'all' end
134
+ def parameters; { format: nil }; end
135
+ self
136
+ EVAL
137
+ end
138
+ end
139
+
140
+ class ActionCacheTest < ActionController::TestCase
141
+ tests ActionCachingTestController
142
+
143
+ def setup
144
+ super
145
+ @request.host = 'hostname.com'
146
+ FileUtils.mkdir_p(FILE_STORE_PATH)
147
+ @path_class = ActionController::Caching::Actions::ActionCachePath
148
+ @mock_controller = ActionCachingMockController.new
149
+ end
150
+
151
+ def teardown
152
+ super
153
+ FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
154
+ end
155
+
156
+ def test_simple_action_cache_with_http_head
157
+ head :index
158
+ assert_response :success
159
+ cached_time = content_to_cache
160
+ assert_equal cached_time, @response.body
161
+ assert fragment_exist?('hostname.com/action_caching_test')
162
+
163
+ head :index
164
+ assert_response :success
165
+ assert_equal cached_time, @response.body
166
+ end
167
+
168
+ def test_simple_action_cache
169
+ get :index
170
+ assert_response :success
171
+ cached_time = content_to_cache
172
+ assert_equal cached_time, @response.body
173
+ assert fragment_exist?('hostname.com/action_caching_test')
174
+
175
+ get :index
176
+ assert_response :success
177
+ assert_equal cached_time, @response.body
178
+ end
179
+
180
+ def test_simple_action_not_cached
181
+ get :destroy
182
+ assert_response :success
183
+ cached_time = content_to_cache
184
+ assert_equal cached_time, @response.body
185
+ assert !fragment_exist?('hostname.com/action_caching_test/destroy')
186
+
187
+ get :destroy
188
+ assert_response :success
189
+ assert_not_equal cached_time, @response.body
190
+ end
191
+
192
+ include RackTestUtils
193
+
194
+ def test_action_cache_with_layout
195
+ get :with_layout
196
+ assert_response :success
197
+ cached_time = content_to_cache
198
+ assert_not_equal cached_time, @response.body
199
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout')
200
+
201
+ get :with_layout
202
+ assert_response :success
203
+ assert_not_equal cached_time, @response.body
204
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout'))
205
+ assert_equal @response.body, body
206
+ end
207
+
208
+ def test_action_cache_with_layout_and_layout_cache_false
209
+ get :layout_false
210
+ assert_response :success
211
+ cached_time = content_to_cache
212
+ assert_not_equal cached_time, @response.body
213
+ assert fragment_exist?('hostname.com/action_caching_test/layout_false')
214
+
215
+ get :layout_false
216
+ assert_response :success
217
+ assert_not_equal cached_time, @response.body
218
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false'))
219
+ assert_equal cached_time, body
220
+ end
221
+
222
+ def test_action_cache_with_layout_and_layout_cache_false_via_proc
223
+ get :with_layout_proc_param, layout: false
224
+ assert_response :success
225
+ cached_time = content_to_cache
226
+ assert_not_equal cached_time, @response.body
227
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
228
+
229
+ get :with_layout_proc_param, layout: false
230
+ assert_response :success
231
+ assert_not_equal cached_time, @response.body
232
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
233
+ assert_equal cached_time, body
234
+ end
235
+
236
+ def test_action_cache_with_layout_and_layout_cache_true_via_proc
237
+ get :with_layout_proc_param, layout: true
238
+ assert_response :success
239
+ cached_time = content_to_cache
240
+ assert_not_equal cached_time, @response.body
241
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
242
+
243
+ get :with_layout_proc_param, layout: true
244
+ assert_response :success
245
+ assert_not_equal cached_time, @response.body
246
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
247
+ assert_equal @response.body, body
248
+ end
249
+
250
+ def test_action_cache_conditional_options
251
+ @request.env['HTTP_ACCEPT'] = 'application/json'
252
+ get :index
253
+ assert_response :success
254
+ assert !fragment_exist?('hostname.com/action_caching_test')
255
+ end
256
+
257
+ def test_action_cache_with_format_and_http_param
258
+ get :with_format_and_http_param, format: 'json'
259
+ assert_response :success
260
+ assert !fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value.json')
261
+ assert fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value')
262
+ end
263
+
264
+ def test_action_cache_with_store_options
265
+ MockTime.expects(:now).returns(12345).once
266
+ @controller.expects(:read_fragment).with('hostname.com/action_caching_test', expires_in: 1.hour).once
267
+ @controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', expires_in: 1.hour).once
268
+ get :index
269
+ assert_response :success
270
+ end
271
+
272
+ def test_action_cache_with_custom_cache_path
273
+ get :show
274
+ assert_response :success
275
+ cached_time = content_to_cache
276
+ assert_equal cached_time, @response.body
277
+ assert fragment_exist?('test.host/custom/show')
278
+
279
+ get :show
280
+ assert_response :success
281
+ assert_equal cached_time, @response.body
282
+ end
283
+
284
+ def test_action_cache_with_custom_cache_path_in_block
285
+ get :edit
286
+ assert_response :success
287
+ assert fragment_exist?('test.host/edit')
288
+
289
+ get :edit, id: 1
290
+ assert_response :success
291
+ assert fragment_exist?('test.host/1;edit')
292
+ end
293
+
294
+ def test_cache_expiration
295
+ get :index
296
+ assert_response :success
297
+ cached_time = content_to_cache
298
+
299
+ get :index
300
+ assert_response :success
301
+ assert_equal cached_time, @response.body
302
+
303
+ get :expire
304
+ assert_response :success
305
+
306
+ get :index
307
+ assert_response :success
308
+ new_cached_time = content_to_cache
309
+ assert_not_equal cached_time, @response.body
310
+
311
+ get :index
312
+ assert_response :success
313
+ assert_equal new_cached_time, @response.body
314
+ end
315
+
316
+ def test_cache_expiration_isnt_affected_by_request_format
317
+ get :index
318
+ cached_time = content_to_cache
319
+
320
+ @request.request_uri = "/action_caching_test/expire.xml"
321
+ get :expire, format: :xml
322
+ assert_response :success
323
+
324
+ get :index
325
+ assert_response :success
326
+ assert_not_equal cached_time, @response.body
327
+ end
328
+
329
+ def test_cache_expiration_with_url_string
330
+ get :index
331
+ cached_time = content_to_cache
332
+
333
+ @request.request_uri = "/action_caching_test/expire_with_url_string"
334
+ get :expire_with_url_string
335
+ assert_response :success
336
+
337
+ get :index
338
+ assert_response :success
339
+ assert_not_equal cached_time, @response.body
340
+ end
341
+
342
+ def test_cache_is_scoped_by_subdomain
343
+ @request.host = 'jamis.hostname.com'
344
+ get :index
345
+ assert_response :success
346
+ jamis_cache = content_to_cache
347
+
348
+ @request.host = 'david.hostname.com'
349
+ get :index
350
+ assert_response :success
351
+ david_cache = content_to_cache
352
+ assert_not_equal jamis_cache, @response.body
353
+
354
+ @request.host = 'jamis.hostname.com'
355
+ get :index
356
+ assert_response :success
357
+ assert_equal jamis_cache, @response.body
358
+
359
+ @request.host = 'david.hostname.com'
360
+ get :index
361
+ assert_response :success
362
+ assert_equal david_cache, @response.body
363
+ end
364
+
365
+ def test_redirect_is_not_cached
366
+ get :redirected
367
+ assert_response :redirect
368
+ get :redirected
369
+ assert_response :redirect
370
+ end
371
+
372
+ def test_forbidden_is_not_cached
373
+ get :forbidden
374
+ assert_response :forbidden
375
+ get :forbidden
376
+ assert_response :forbidden
377
+ end
378
+
379
+ def test_xml_version_of_resource_is_treated_as_different_cache
380
+ with_routing do |set|
381
+ set.draw do
382
+ get ':controller(/:action(.:format))'
383
+ end
384
+
385
+ get :index, format: 'xml'
386
+ assert_response :success
387
+ cached_time = content_to_cache
388
+ assert_equal cached_time, @response.body
389
+ assert fragment_exist?('hostname.com/action_caching_test/index.xml')
390
+
391
+ get :index, format: 'xml'
392
+ assert_response :success
393
+ assert_equal cached_time, @response.body
394
+ assert_equal 'application/xml', @response.content_type
395
+
396
+ get :expire_xml
397
+ assert_response :success
398
+
399
+ get :index, format: 'xml'
400
+ assert_response :success
401
+ assert_not_equal cached_time, @response.body
402
+ end
403
+ end
404
+
405
+ def test_correct_content_type_is_returned_for_cache_hit
406
+ # run it twice to cache it the first time
407
+ get :index, id: 'content-type', format: 'xml'
408
+ get :index, id: 'content-type', format: 'xml'
409
+ assert_response :success
410
+ assert_equal 'application/xml', @response.content_type
411
+ end
412
+
413
+ def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key
414
+ # run it twice to cache it the first time
415
+ get :show, format: 'xml'
416
+ get :show, format: 'xml'
417
+ assert_response :success
418
+ assert_equal 'application/xml', @response.content_type
419
+ end
420
+
421
+ def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key_from_proc
422
+ # run it twice to cache it the first time
423
+ get :edit, id: 1, format: 'xml'
424
+ get :edit, id: 1, format: 'xml'
425
+ assert_response :success
426
+ assert_equal 'application/xml', @response.content_type
427
+ end
428
+
429
+ def test_empty_path_is_normalized
430
+ @mock_controller.mock_url_for = 'http://example.org/'
431
+ @mock_controller.mock_path = '/'
432
+
433
+ assert_equal 'example.org/index', @path_class.new(@mock_controller, {}).path
434
+ end
435
+
436
+ def test_file_extensions
437
+ get :index, id: 'kitten.jpg'
438
+ get :index, id: 'kitten.jpg'
439
+
440
+ assert_response :success
441
+ end
442
+
443
+ if defined? ActiveRecord
444
+ def test_record_not_found_returns_404_for_multiple_requests
445
+ get :record_not_found
446
+ assert_response 404
447
+ get :record_not_found
448
+ assert_response 404
449
+ end
450
+ end
451
+
452
+ def test_four_oh_four_returns_404_for_multiple_requests
453
+ get :four_oh_four
454
+ assert_response 404
455
+ get :four_oh_four
456
+ assert_response 404
457
+ end
458
+
459
+ def test_four_oh_four_renders_content
460
+ get :four_oh_four
461
+ assert_equal "404'd!", @response.body
462
+ end
463
+
464
+ def test_simple_runtime_error_returns_500_for_multiple_requests
465
+ get :simple_runtime_error
466
+ assert_response 500
467
+ get :simple_runtime_error
468
+ assert_response 500
469
+ end
470
+
471
+ def test_action_caching_plus_streaming
472
+ get :streaming
473
+ assert_response :success
474
+ assert_match(/streaming/, @response.body)
475
+ assert fragment_exist?('hostname.com/action_caching_test/streaming')
476
+ end
477
+
478
+ def test_invalid_format_returns_not_acceptable
479
+ get :invalid, format: 'json'
480
+ assert_response :success
481
+ cached_time = content_to_cache
482
+ assert_equal cached_time, @response.body
483
+
484
+ assert fragment_exist?("hostname.com/action_caching_test/invalid.json")
485
+
486
+ get :invalid, format: 'json'
487
+ assert_response :success
488
+ assert_equal cached_time, @response.body
489
+
490
+ get :invalid, format: 'xml'
491
+ assert_response :not_acceptable
492
+
493
+ get :invalid, format: '\xC3\x83'
494
+ assert_response :not_acceptable
495
+ end
496
+
497
+ private
498
+
499
+ def content_to_cache
500
+ assigns(:cache_this)
501
+ end
502
+
503
+ def fragment_exist?(path)
504
+ @controller.fragment_exist?(path)
505
+ end
506
+
507
+ def read_fragment(path)
508
+ @controller.read_fragment(path)
509
+ end
510
+ end
@@ -0,0 +1,2 @@
1
+ <title><%= @title || yield(:title) %></title>
2
+ <%= yield -%>
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: actionpack-action_caching
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Heinemeier Hansson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: actionpack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 4.0.0.beta
22
+ - - <
23
+ - !ruby/object:Gem::Version
24
+ version: '5.0'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 4.0.0.beta
33
+ - - <
34
+ - !ruby/object:Gem::Version
35
+ version: '5.0'
36
+ - !ruby/object:Gem::Dependency
37
+ name: mocha
38
+ requirement: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ - !ruby/object:Gem::Dependency
53
+ name: activerecord
54
+ requirement: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: 4.0.0.beta
60
+ - - <
61
+ - !ruby/object:Gem::Version
62
+ version: '5.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: 4.0.0.beta
71
+ - - <
72
+ - !ruby/object:Gem::Version
73
+ version: '5.0'
74
+ description: Action caching for Action Pack (removed from core in Rails 4.0)
75
+ email: david@loudthinking.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - .gitignore
81
+ - .travis.yml
82
+ - Gemfile
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - actionpack-action_caching.gemspec
87
+ - lib/action_controller/action_caching.rb
88
+ - lib/action_controller/caching/actions.rb
89
+ - lib/actionpack/action_caching.rb
90
+ - test/abstract_unit.rb
91
+ - test/caching_test.rb
92
+ - test/fixtures/layouts/talk_from_action.erb
93
+ homepage: https://github.com/rails/actionpack-action_caching
94
+ licenses: []
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 1.8.23
114
+ signing_key:
115
+ specification_version: 3
116
+ summary: Action caching for Action Pack (removed from core in Rails 4.0)
117
+ test_files:
118
+ - test/abstract_unit.rb
119
+ - test/caching_test.rb
120
+ - test/fixtures/layouts/talk_from_action.erb
121
+ has_rdoc: