tennpipes-memory 3.6.6
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.
- 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
|