tennpipes-memory 3.6.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +346 -0
- data/Rakefile +1 -0
- data/lib/tennpipes-memory.rb +125 -0
- data/lib/tennpipes-memory/helpers/cache_object.rb +23 -0
- data/lib/tennpipes-memory/helpers/cache_store.rb +16 -0
- data/lib/tennpipes-memory/helpers/fragment.rb +71 -0
- data/lib/tennpipes-memory/helpers/page.rb +169 -0
- data/test/helper.rb +24 -0
- data/test/test_moneta_store.rb +53 -0
- data/test/test_tennpipes_cache.rb +478 -0
- metadata +101 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tennpipes
|
2
|
+
module Cache
|
3
|
+
module Helpers
|
4
|
+
module ObjectCache
|
5
|
+
def cache_object(key, opts = {})
|
6
|
+
if settings.caching?
|
7
|
+
began_at = Time.now
|
8
|
+
if value = settings.cache[key.to_s]
|
9
|
+
logger.debug "GET Object", began_at, key.to_s if defined?(logger)
|
10
|
+
else
|
11
|
+
value = yield
|
12
|
+
settings.cache.store(key.to_s, value, opts)
|
13
|
+
logger.debug "SET Object", began_at, key.to_s if defined?(logger)
|
14
|
+
end
|
15
|
+
value
|
16
|
+
else
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tennpipes
|
2
|
+
module Cache
|
3
|
+
module Helpers
|
4
|
+
module CacheStore
|
5
|
+
def expire(*key)
|
6
|
+
if key.size == 1 and (key.first.is_a?(String) or key.first.is_a?(Symbol))
|
7
|
+
settings.cache.delete(key.first)
|
8
|
+
else
|
9
|
+
settings.cache.delete(self.class.url(*key))
|
10
|
+
end
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Tennpipes
|
2
|
+
module Cache
|
3
|
+
module Helpers
|
4
|
+
##
|
5
|
+
# Whereas page-level caching, described in the first section of this document, works by
|
6
|
+
# grabbing the entire output of a route, fragment caching gives the developer fine-grained
|
7
|
+
# control of what gets cached. This type of caching occurs at whatever level you choose.
|
8
|
+
#
|
9
|
+
# Possible uses for fragment caching might include:
|
10
|
+
#
|
11
|
+
# - a 'feed' of some items on a page
|
12
|
+
# - output fetched (by proxy) from an API on a third-party site
|
13
|
+
# - parts of your page which are largely static/do not need re-rendering every request
|
14
|
+
# - any output which is expensive to render
|
15
|
+
#
|
16
|
+
module Fragment
|
17
|
+
include Tennpipes::Helpers::OutputHelpers
|
18
|
+
|
19
|
+
##
|
20
|
+
# This helper is used anywhere in your application you would like to associate a fragment
|
21
|
+
# to be cached. It can be used in within a route:
|
22
|
+
#
|
23
|
+
# @param [String] key
|
24
|
+
# cache key
|
25
|
+
# @param [Hash] opts
|
26
|
+
# cache options, e.g :expires
|
27
|
+
# @param [Proc]
|
28
|
+
# Execution result to store in the cache
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# # Caching a fragment
|
32
|
+
# class MyTweets < Tennpipes::Application
|
33
|
+
# enable :caching # turns on caching mechanism
|
34
|
+
#
|
35
|
+
# controller '/tweets' do
|
36
|
+
# get :feed, :map => '/:username' do
|
37
|
+
# username = params[:username]
|
38
|
+
#
|
39
|
+
# @feed = cache( "feed_for_#{username}", :expires => 3 ) do
|
40
|
+
# @tweets = Tweet.all( :username => username )
|
41
|
+
# render 'partials/feedcontent'
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# # Below outputs @feed somewhere in its markup.
|
45
|
+
# render 'feeds/show'
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def cache(key, opts = {}, &block)
|
52
|
+
if settings.caching?
|
53
|
+
began_at = Time.now
|
54
|
+
if value = settings.cache[key.to_s]
|
55
|
+
logger.debug "GET Fragment", began_at, key.to_s if defined?(logger)
|
56
|
+
concat_content(value.html_safe)
|
57
|
+
else
|
58
|
+
value = capture_html(&block)
|
59
|
+
settings.cache.store(key.to_s, value, opts)
|
60
|
+
logger.debug "SET Fragment", began_at, key.to_s if defined?(logger)
|
61
|
+
concat_content(value)
|
62
|
+
end
|
63
|
+
else
|
64
|
+
value = capture_html(&block)
|
65
|
+
concat_content(value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module Tennpipes
|
2
|
+
module Cache
|
3
|
+
##
|
4
|
+
# Helpers supporting page or fragment caching within a request route.
|
5
|
+
#
|
6
|
+
module Helpers
|
7
|
+
##
|
8
|
+
# Page caching is easy to integrate into your application. To turn it on, simply provide the
|
9
|
+
# <tt>:cache => true</tt> option on either a controller or one of its routes.
|
10
|
+
# By default, cached content is persisted with a "file store" --that is, in a
|
11
|
+
# subdirectory of your application root.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # Setting content expiry time.
|
15
|
+
# class CachedApp < Tennpipes::Application
|
16
|
+
# enable :caching # turns on caching mechanism
|
17
|
+
#
|
18
|
+
# controller '/blog', :cache => true do
|
19
|
+
# expires 15
|
20
|
+
#
|
21
|
+
# get '/entries' do
|
22
|
+
# # expires 15 => can also be defined inside a single route
|
23
|
+
# 'Just broke up eating twinkies, lol'
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# get '/post/:id' do
|
27
|
+
# cache_key :my_name
|
28
|
+
# @post = Post.find(params[:id])
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# You can manually expire cache with CachedApp.cache.delete(:my_name)
|
34
|
+
#
|
35
|
+
# Note that the "latest" method call to <tt>expires</tt> determines its value: if
|
36
|
+
# called within a route, as opposed to a controller definition, the route's
|
37
|
+
# value will be assumed.
|
38
|
+
#
|
39
|
+
module Page
|
40
|
+
##
|
41
|
+
# This helper is used within a controller or route to indicate how often content
|
42
|
+
# should persist in the cache.
|
43
|
+
#
|
44
|
+
# After <tt>seconds</tt> seconds have passed, content previously cached will
|
45
|
+
# be discarded and re-rendered. Code associated with that route will <em>not</em>
|
46
|
+
# be executed; rather, its previous output will be sent to the client with a
|
47
|
+
# 200 OK status code.
|
48
|
+
#
|
49
|
+
# @param [Integer] time
|
50
|
+
# Time til expiration (seconds)
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# controller '/blog', :cache => true do
|
54
|
+
# expires 15
|
55
|
+
#
|
56
|
+
# get '/entries' do
|
57
|
+
# 'Just broke up eating twinkies, lol'
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def expires(time)
|
63
|
+
@route.cache_expires = time
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# This helper is used within a route or route to indicate the name in the cache.
|
68
|
+
#
|
69
|
+
# @param [Symbol] name
|
70
|
+
# cache key
|
71
|
+
# @param [Proc] block
|
72
|
+
# block to be evaluated to cache key
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
# controller '/blog', :cache => true do
|
76
|
+
#
|
77
|
+
# get '/post/:id' do
|
78
|
+
# cache_key :my_name
|
79
|
+
# @post = Post.find(params[:id])
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# get '/foo', :cache => true do
|
85
|
+
# cache_key { param[:id] }
|
86
|
+
# "My id is #{param[:id}"
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
def cache_key(name = nil, &block)
|
91
|
+
fail "Can not provide both cache_key and a block" if name && block
|
92
|
+
@route.cache_key = name || block
|
93
|
+
end
|
94
|
+
|
95
|
+
CACHED_VERBS = { 'GET' => true, 'HEAD' => true }.freeze
|
96
|
+
|
97
|
+
def self.tennpipes_route_added(route, verb, *)
|
98
|
+
return unless route.cache && CACHED_VERBS[verb]
|
99
|
+
|
100
|
+
route.before_filters do
|
101
|
+
next unless settings.caching?
|
102
|
+
if cached_response = load_cached_response
|
103
|
+
content_type cached_response[:content_type]
|
104
|
+
halt 200, cached_response[:body]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
route.after_filters do
|
109
|
+
save_cached_response(route.cache_expires) if settings.caching?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def load_cached_response
|
116
|
+
began_at = Time.now
|
117
|
+
route_cache_key = resolve_cache_key || env['PATH_INFO']
|
118
|
+
|
119
|
+
value = settings.cache[route_cache_key]
|
120
|
+
logger.debug "GET Cache", began_at, route_cache_key if defined?(logger) && value
|
121
|
+
|
122
|
+
value
|
123
|
+
end
|
124
|
+
|
125
|
+
def save_cached_response(cache_expires)
|
126
|
+
return unless @_response_buffer.kind_of?(String)
|
127
|
+
|
128
|
+
began_at = Time.now
|
129
|
+
route_cache_key = resolve_cache_key || env['PATH_INFO']
|
130
|
+
|
131
|
+
content = {
|
132
|
+
:body => @_response_buffer,
|
133
|
+
:content_type => @_content_type
|
134
|
+
}
|
135
|
+
|
136
|
+
settings.cache.store(route_cache_key, content, :expires => cache_expires)
|
137
|
+
|
138
|
+
logger.debug "SET Cache", began_at, route_cache_key if defined?(logger)
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Resolve the cache_key when it's a block in the correct context.
|
143
|
+
#
|
144
|
+
def resolve_cache_key
|
145
|
+
key = @route.cache_key
|
146
|
+
key.is_a?(Proc) ? instance_eval(&key) : key
|
147
|
+
end
|
148
|
+
|
149
|
+
module ClassMethods
|
150
|
+
##
|
151
|
+
# A method to set `expires` time inside `controller` blocks.
|
152
|
+
#
|
153
|
+
# @example
|
154
|
+
# controller :users do
|
155
|
+
# expires 15
|
156
|
+
#
|
157
|
+
# get :show do
|
158
|
+
# 'shown'
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
def expires(time)
|
163
|
+
@_expires = time
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
TENNPIPES_ROOT = File.dirname(__FILE__) unless defined?(TENNPIPES_ROOT)
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/pride'
|
6
|
+
require 'rack/test'
|
7
|
+
require 'tennpipes-memory'
|
8
|
+
|
9
|
+
require 'ext/rack-test-methods'
|
10
|
+
|
11
|
+
class MiniTest::Spec
|
12
|
+
include Rack::Test::Methods
|
13
|
+
|
14
|
+
# Sets up a Sinatra::Base subclass defined with the block
|
15
|
+
# given. Used in setup or individual spec methods to establish
|
16
|
+
# the application.
|
17
|
+
def mock_app(base=Tennpipes::Application, &block)
|
18
|
+
@app = Sinatra.new(base, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def app
|
22
|
+
Rack::Lint.new(@app)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/helper')
|
2
|
+
|
3
|
+
describe 'Tennpipes::Cache - Moneta store' do
|
4
|
+
def setup
|
5
|
+
@test_key = "val_#{Time.now.to_i}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
Tennpipes.cache.clear
|
10
|
+
end
|
11
|
+
|
12
|
+
class Foo
|
13
|
+
def bar; "bar"; end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "return nil trying to get a value that doesn't exist" do
|
17
|
+
Tennpipes.cache.clear
|
18
|
+
assert_equal nil, Tennpipes.cache[@test_key]
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'set and get an object with marshal' do
|
22
|
+
Tennpipes.cache[@test_key] = Foo.new
|
23
|
+
assert_equal "bar", Tennpipes.cache[@test_key].bar
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'set and get a nil value' do
|
27
|
+
Tennpipes.cache[@test_key] = nil
|
28
|
+
assert_equal '', Tennpipes.cache[@test_key].to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'set and get a raw value' do
|
32
|
+
Tennpipes.cache[@test_key] = 'foo'
|
33
|
+
assert_equal 'foo', Tennpipes.cache[@test_key]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "set a value that expires" do
|
37
|
+
init_time = ( Time.now - 20 )
|
38
|
+
Time.stub(:now, init_time) { Tennpipes.cache.store(@test_key, 'test', :expires => 1) }
|
39
|
+
Time.stub(:now, init_time + 20) { assert_equal nil, Tennpipes.cache[@test_key] }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "be able to cache forever" do
|
43
|
+
Tennpipes.cache.store('forever', 'cached', :expires => false)
|
44
|
+
2.times { |i| assert_equal 'cached', Tennpipes.cache['forever'] }
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'delete a value' do
|
48
|
+
Tennpipes.cache[@test_key] = 'test'
|
49
|
+
assert_equal 'test', Tennpipes.cache[@test_key]
|
50
|
+
Tennpipes.cache.delete(@test_key)
|
51
|
+
assert_equal nil, Tennpipes.cache[@test_key]
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,478 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
describe "TennpipesCache" do
|
4
|
+
after do
|
5
|
+
tmp = File.expand_path("../tmp", __FILE__)
|
6
|
+
%x[rm -rf #{tmp}]
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should cache a fragment' do
|
10
|
+
called = false
|
11
|
+
mock_app do
|
12
|
+
register Tennpipes::Cache
|
13
|
+
enable :caching
|
14
|
+
get("/foo"){ cache(:test) { called ? halt(500) : (called = 'test fragment') } }
|
15
|
+
end
|
16
|
+
get "/foo"
|
17
|
+
assert_equal 200, status
|
18
|
+
assert_equal 'test fragment', body
|
19
|
+
get "/foo"
|
20
|
+
assert_equal 200, status
|
21
|
+
assert_equal 'test fragment', body
|
22
|
+
refute_equal called, false
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should cache a page' do
|
26
|
+
called = false
|
27
|
+
mock_app do
|
28
|
+
register Tennpipes::Cache
|
29
|
+
enable :caching
|
30
|
+
get('/foo', :cache => true){ called ? halt(500) : (called = 'test page') }
|
31
|
+
end
|
32
|
+
get "/foo"
|
33
|
+
assert_equal 200, status
|
34
|
+
assert_equal 'test page', body
|
35
|
+
get "/foo"
|
36
|
+
assert_equal 200, status
|
37
|
+
assert_equal 'test page', body
|
38
|
+
refute_equal false, called
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should cache HEAD verb' do
|
42
|
+
called_times = 0
|
43
|
+
mock_app do
|
44
|
+
register Tennpipes::Cache
|
45
|
+
enable :caching
|
46
|
+
get('/foo', :cache => true){ called_times += 1; called_times.to_s }
|
47
|
+
end
|
48
|
+
head "/foo"
|
49
|
+
head "/foo"
|
50
|
+
assert_equal 1, called_times
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should not cache POST verb' do
|
54
|
+
called_times = 0
|
55
|
+
mock_app do
|
56
|
+
register Tennpipes::Cache
|
57
|
+
enable :caching
|
58
|
+
post('/foo', :cache => true){ called_times += 1; called_times.to_s }
|
59
|
+
end
|
60
|
+
post "/foo"
|
61
|
+
assert_equal 1, called_times
|
62
|
+
post "/foo"
|
63
|
+
assert_equal 2, called_times
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should not cache DELETE verb' do
|
67
|
+
called_times = 0
|
68
|
+
mock_app do
|
69
|
+
register Tennpipes::Cache
|
70
|
+
enable :caching
|
71
|
+
delete('/foo', :cache => true){ called_times += 1; called_times.to_s }
|
72
|
+
end
|
73
|
+
delete "/foo"
|
74
|
+
assert_equal 1, called_times
|
75
|
+
delete "/foo"
|
76
|
+
assert_equal 2, called_times
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should not cache PUT verb' do
|
80
|
+
called_times = 0
|
81
|
+
mock_app do
|
82
|
+
register Tennpipes::Cache
|
83
|
+
enable :caching
|
84
|
+
put('/foo', :cache => true){ called_times += 1; called_times.to_s }
|
85
|
+
end
|
86
|
+
put "/foo"
|
87
|
+
assert_equal 1, called_times
|
88
|
+
put "/foo"
|
89
|
+
assert_equal 2, called_times
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should delete from the cache' do
|
93
|
+
called = false
|
94
|
+
mock_app do
|
95
|
+
register Tennpipes::Cache
|
96
|
+
enable :caching
|
97
|
+
get('/foo', :cache => true){ called ? 'test page again' : (called = 'test page') }
|
98
|
+
get('/delete_foo'){ expire('/foo') }
|
99
|
+
end
|
100
|
+
get "/foo"
|
101
|
+
assert_equal 200, status
|
102
|
+
assert_equal 'test page', body
|
103
|
+
get "/delete_foo"
|
104
|
+
get "/foo"
|
105
|
+
assert_equal 200, status
|
106
|
+
assert_equal 'test page again', body
|
107
|
+
refute_equal false, called
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should accept custom cache keys' do
|
111
|
+
called = false
|
112
|
+
mock_app do
|
113
|
+
register Tennpipes::Cache
|
114
|
+
enable :caching
|
115
|
+
get '/foo', :cache => true do
|
116
|
+
if called
|
117
|
+
"you'll never see me"
|
118
|
+
else
|
119
|
+
cache_key :foo
|
120
|
+
called = 'foo'
|
121
|
+
|
122
|
+
called
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
get '/bar', :cache => true do
|
127
|
+
if called
|
128
|
+
cache_key :bar
|
129
|
+
called = 'bar'
|
130
|
+
|
131
|
+
called
|
132
|
+
else
|
133
|
+
"you'll never see me"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
get "/foo"
|
138
|
+
assert_equal 200, status
|
139
|
+
assert_equal 'foo', body
|
140
|
+
assert_equal 'foo', @app.cache[:foo][:body]
|
141
|
+
get "/foo"
|
142
|
+
assert_equal 'foo', body
|
143
|
+
|
144
|
+
get "/bar"
|
145
|
+
assert_equal 200, status
|
146
|
+
assert_equal 'bar', body
|
147
|
+
assert_equal 'bar', @app.cache[:bar][:body]
|
148
|
+
get "/bar"
|
149
|
+
assert_equal 'bar', body
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should delete based on urls' do
|
153
|
+
called = false
|
154
|
+
mock_app do
|
155
|
+
register Tennpipes::Cache
|
156
|
+
enable :caching
|
157
|
+
get(:foo, :with => :id, :cache => true) { called ? 'test page again' : (called = 'test page') }
|
158
|
+
get(:delete_foo, :with => :id) { expire(:foo, params[:id]) }
|
159
|
+
end
|
160
|
+
get "/foo/12"
|
161
|
+
assert_equal 200, status
|
162
|
+
assert_equal 'test page', body
|
163
|
+
get "/delete_foo/12"
|
164
|
+
get "/foo/12"
|
165
|
+
assert_equal 200, status
|
166
|
+
assert_equal 'test page again', body
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should allow controller-wide caching' do
|
170
|
+
called = false
|
171
|
+
mock_app do
|
172
|
+
controller :cache => true do
|
173
|
+
register Tennpipes::Cache
|
174
|
+
enable :caching
|
175
|
+
get("/foo"){ called ? halt(500) : (called = 'test') }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
get "/foo"
|
179
|
+
assert_equal 200, status
|
180
|
+
assert_equal 'test', body
|
181
|
+
get "/foo"
|
182
|
+
assert_equal 200, status
|
183
|
+
assert_equal 'test', body
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should allow controller-wide expires' do
|
187
|
+
called = false
|
188
|
+
mock_app do
|
189
|
+
register Tennpipes::Cache
|
190
|
+
controller :cache => true do
|
191
|
+
enable :caching
|
192
|
+
expires 1
|
193
|
+
get("/foo"){ called ? halt(500) : (called = 'test') }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
get "/foo"
|
197
|
+
assert_equal 200, status
|
198
|
+
assert_equal 'test', body
|
199
|
+
get "/foo"
|
200
|
+
assert_equal 200, status
|
201
|
+
assert_equal 'test', body
|
202
|
+
Time.stub(:now, Time.now + 2) { get "/foo" }
|
203
|
+
assert_equal 500, status
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'should allow cache disabling on a per route basis' do
|
207
|
+
called = false
|
208
|
+
mock_app do
|
209
|
+
register Tennpipes::Cache
|
210
|
+
enable :caching
|
211
|
+
controller :cache => true do
|
212
|
+
get("/foo", :cache => false){ called ? 'test again' : (called = 'test') }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
get "/foo"
|
216
|
+
assert_equal 200, status
|
217
|
+
assert_equal 'test', body
|
218
|
+
get "/foo"
|
219
|
+
assert_equal 200, status
|
220
|
+
assert_equal 'test again', body
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'should allow expiring for pages' do
|
224
|
+
called = false
|
225
|
+
mock_app do
|
226
|
+
register Tennpipes::Cache
|
227
|
+
enable :caching
|
228
|
+
controller :cache => true do
|
229
|
+
get("/foo") {
|
230
|
+
expires 1
|
231
|
+
called ? 'test again' : (called = 'test')
|
232
|
+
}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
get "/foo"
|
236
|
+
assert_equal 200, status
|
237
|
+
assert_equal 'test', body
|
238
|
+
get "/foo"
|
239
|
+
assert_equal 200, status
|
240
|
+
assert_equal 'test', body
|
241
|
+
Time.stub(:now, Time.now + 3) { get "/foo" }
|
242
|
+
assert_equal 200, status
|
243
|
+
assert_equal 'test again', body
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should allow expiring for fragments' do
|
247
|
+
called = false
|
248
|
+
mock_app do
|
249
|
+
register Tennpipes::Cache
|
250
|
+
enable :caching
|
251
|
+
controller do
|
252
|
+
get("/foo") {
|
253
|
+
expires 1
|
254
|
+
cache(:test, :expires => 2) do
|
255
|
+
called ? 'test again' : (called = 'test')
|
256
|
+
end
|
257
|
+
}
|
258
|
+
end
|
259
|
+
end
|
260
|
+
get "/foo"
|
261
|
+
assert_equal 200, status
|
262
|
+
assert_equal 'test', body
|
263
|
+
get "/foo"
|
264
|
+
assert_equal 200, status
|
265
|
+
assert_equal 'test', body
|
266
|
+
Time.stub(:now, Time.now + 3) { get "/foo" }
|
267
|
+
assert_equal 200, status
|
268
|
+
assert_equal 'test again', body
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'should allow disabling of the cache' do
|
272
|
+
called = false
|
273
|
+
mock_app do
|
274
|
+
register Tennpipes::Cache
|
275
|
+
disable :caching
|
276
|
+
controller :cache => true do
|
277
|
+
get("/foo"){ called ? halt(500) : (called = 'test') }
|
278
|
+
end
|
279
|
+
end
|
280
|
+
get "/foo"
|
281
|
+
assert_equal 200, status
|
282
|
+
assert_equal 'test', body
|
283
|
+
get "/foo"
|
284
|
+
assert_equal 500, status
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'should not cache integer statuses' do
|
288
|
+
mock_app do
|
289
|
+
register Tennpipes::Cache
|
290
|
+
enable :caching
|
291
|
+
get( '/404', :cache => true ) { not_found }
|
292
|
+
get( '/503', :cache => true ) { error 503 }
|
293
|
+
not_found { 'fancy 404' }
|
294
|
+
error( 503 ) { 'fancy 503' }
|
295
|
+
end
|
296
|
+
get '/404'
|
297
|
+
assert_equal 'fancy 404', body
|
298
|
+
assert_equal 404, status
|
299
|
+
assert_equal nil, @app.cache['/404']
|
300
|
+
get '/404'
|
301
|
+
assert_equal 'fancy 404', body
|
302
|
+
assert_equal 404, status
|
303
|
+
get '/503'
|
304
|
+
assert_equal 'fancy 503', body
|
305
|
+
assert_equal 503, status
|
306
|
+
assert_equal nil, @app.cache['/503']
|
307
|
+
get '/503'
|
308
|
+
assert_equal 'fancy 503', body
|
309
|
+
assert_equal 503, status
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'should cache should not hit with unique params' do
|
313
|
+
call_count = 0
|
314
|
+
mock_app do
|
315
|
+
register Tennpipes::Cache
|
316
|
+
enable :caching
|
317
|
+
before do
|
318
|
+
param = params[:test] || 'none'
|
319
|
+
cache_key "foo?#{param}"
|
320
|
+
end
|
321
|
+
get '/foo/:test', :cache => true do
|
322
|
+
param = params[:test] || 'none'
|
323
|
+
call_count += 1
|
324
|
+
"foo?#{param}"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
get '/foo/none'
|
329
|
+
get '/foo/none'
|
330
|
+
assert_equal 200, status
|
331
|
+
assert_equal 'foo?none', body
|
332
|
+
assert_equal 1, call_count
|
333
|
+
|
334
|
+
get '/foo/yes'
|
335
|
+
assert_equal 'foo?yes', body
|
336
|
+
assert_equal 2, call_count
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'should resolve block cache keys' do
|
340
|
+
call_count = 0
|
341
|
+
mock_app do
|
342
|
+
register Tennpipes::Cache
|
343
|
+
enable :caching
|
344
|
+
|
345
|
+
get '/foo', :cache => true do
|
346
|
+
cache_key { "key #{params[:id]}" }
|
347
|
+
call_count += 1
|
348
|
+
params[:id]
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
get '/foo?id=1'
|
353
|
+
get '/foo?id=2'
|
354
|
+
get '/foo?id=2'
|
355
|
+
get '/foo?id=1&something_else=42'
|
356
|
+
get '/foo?id=3&something_else=42'
|
357
|
+
|
358
|
+
assert_equal 3, call_count
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'should raise an error if providing both a cache_key and block' do
|
362
|
+
mock_app do
|
363
|
+
register Tennpipes::Cache
|
364
|
+
enable :caching
|
365
|
+
|
366
|
+
get '/foo', :cache => true do
|
367
|
+
cache_key(:some_key) { "key #{params[:id]}" }
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
assert_raises(RuntimeError) { get '/foo' }
|
372
|
+
end
|
373
|
+
|
374
|
+
it 'should cache content_type' do
|
375
|
+
called = false
|
376
|
+
mock_app do
|
377
|
+
register Tennpipes::Cache
|
378
|
+
enable :caching
|
379
|
+
get '/foo', :cache => true do
|
380
|
+
content_type :json
|
381
|
+
if called
|
382
|
+
"you'll never see me"
|
383
|
+
else
|
384
|
+
cache_key :foo
|
385
|
+
called = '{"foo":"bar"}'
|
386
|
+
|
387
|
+
called
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
get "/foo"
|
392
|
+
assert_equal 200, status
|
393
|
+
assert_equal '{"foo":"bar"}', body
|
394
|
+
assert_equal '{"foo":"bar"}', @app.cache[:foo][:body]
|
395
|
+
get "/foo"
|
396
|
+
assert_equal '{"foo":"bar"}', body
|
397
|
+
assert_match /json/, last_response.content_type
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'should cache an object' do
|
401
|
+
counter = 0
|
402
|
+
mock_app do
|
403
|
+
register Tennpipes::Cache
|
404
|
+
enable :caching
|
405
|
+
get '/' do
|
406
|
+
result = ''
|
407
|
+
2.times do
|
408
|
+
result = cache_object 'object1' do
|
409
|
+
counter += 1
|
410
|
+
{ :foo => 'bar' }
|
411
|
+
end
|
412
|
+
end
|
413
|
+
result[:foo].to_s
|
414
|
+
end
|
415
|
+
end
|
416
|
+
get '/'
|
417
|
+
assert_equal 'bar', body
|
418
|
+
assert_equal 1, counter
|
419
|
+
end
|
420
|
+
|
421
|
+
it 'should allow different expiring times for different pages' do
|
422
|
+
skip
|
423
|
+
called_times_a = 0
|
424
|
+
called_times_b = 0
|
425
|
+
mock_app do
|
426
|
+
register Tennpipes::Cache
|
427
|
+
enable :caching
|
428
|
+
controller :cache => true do
|
429
|
+
get("/foo") do
|
430
|
+
expires 1
|
431
|
+
called_times_a += 1
|
432
|
+
called_times_b.to_s
|
433
|
+
end
|
434
|
+
get("/bar") do
|
435
|
+
expires 3
|
436
|
+
called_times_b += 1
|
437
|
+
called_times_b.to_s
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
Time.stub(:now, Time.now) { get "/foo"; get "/bar" }
|
442
|
+
assert_equal 1, called_times_a
|
443
|
+
assert_equal 1, called_times_b
|
444
|
+
Time.stub(:now, Time.now + 0.5) { get "/foo"; get "/bar" }
|
445
|
+
assert_equal 1, called_times_a
|
446
|
+
assert_equal 1, called_times_b
|
447
|
+
Time.stub(:now, Time.now + 2) { get "/foo"; get "/bar" }
|
448
|
+
assert_equal 2, called_times_a
|
449
|
+
assert_equal 1, called_times_b
|
450
|
+
Time.stub(:now, Time.now + 2.5) { get "/foo"; get "/bar" }
|
451
|
+
assert_equal 2, called_times_a
|
452
|
+
assert_equal 1, called_times_b
|
453
|
+
Time.stub(:now, Time.now + 4) { get "/foo"; get "/bar" }
|
454
|
+
assert_equal 3, called_times_a
|
455
|
+
assert_equal 2, called_times_b
|
456
|
+
Time.stub(:now, Time.now + 5.5) { get "/foo"; get "/bar" }
|
457
|
+
assert_equal 4, called_times_a
|
458
|
+
assert_equal 2, called_times_b
|
459
|
+
end
|
460
|
+
|
461
|
+
it "preserve the app's `caching` setting if set before registering the module" do
|
462
|
+
mock_app do
|
463
|
+
enable :caching
|
464
|
+
register Tennpipes::Cache
|
465
|
+
end
|
466
|
+
|
467
|
+
assert @app.caching
|
468
|
+
end
|
469
|
+
|
470
|
+
it "preserve the app's `cache` setting if set before registering the module" do
|
471
|
+
mock_app do
|
472
|
+
set :cache, Tennpipes::Cache.new(:Memory)
|
473
|
+
register Tennpipes::Cache
|
474
|
+
end
|
475
|
+
|
476
|
+
assert @app.cache.adapter.adapter.is_a?(Moneta::Adapters::Memory)
|
477
|
+
end
|
478
|
+
end
|