actionpack-action_caching 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: