merb-cache 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +2 -2
- data/README +207 -143
- data/Rakefile +55 -10
- data/TODO +0 -2
- data/lib/merb-cache/cache.rb +84 -0
- data/lib/merb-cache/core_ext/enumerable.rb +9 -0
- data/lib/merb-cache/core_ext/hash.rb +20 -0
- data/lib/merb-cache/merb_ext/controller.rb +167 -0
- data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
- data/lib/merb-cache/stores/fundamental/file_store.rb +112 -0
- data/lib/merb-cache/stores/fundamental/memcached_store.rb +112 -0
- data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +123 -0
- data/lib/merb-cache/stores/strategy/action_store.rb +56 -0
- data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
- data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
- data/lib/merb-cache/stores/strategy/page_store.rb +64 -0
- data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
- data/lib/merb-cache.rb +8 -7
- data/spec/merb-cache/cache_spec.rb +88 -0
- data/spec/merb-cache/core_ext/enumerable_spec.rb +22 -0
- data/spec/merb-cache/core_ext/hash_spec.rb +20 -0
- data/spec/merb-cache/merb_ext/controller_spec.rb +284 -0
- data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +166 -0
- data/spec/merb-cache/stores/fundamental/file_store_spec.rb +186 -0
- data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +243 -0
- data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
- data/spec/merb-cache/stores/strategy/action_store_spec.rb +189 -0
- data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +225 -0
- data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +51 -0
- data/spec/merb-cache/stores/strategy/page_store_spec.rb +111 -0
- data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +75 -0
- data/spec/spec_helper.rb +69 -72
- metadata +42 -31
- data/lib/merb-cache/cache-action.rb +0 -144
- data/lib/merb-cache/cache-fragment.rb +0 -95
- data/lib/merb-cache/cache-page.rb +0 -203
- data/lib/merb-cache/cache-store/database-activerecord.rb +0 -88
- data/lib/merb-cache/cache-store/database-datamapper.rb +0 -79
- data/lib/merb-cache/cache-store/database-sequel.rb +0 -78
- data/lib/merb-cache/cache-store/database.rb +0 -144
- data/lib/merb-cache/cache-store/dummy.rb +0 -106
- data/lib/merb-cache/cache-store/file.rb +0 -194
- data/lib/merb-cache/cache-store/memcache.rb +0 -199
- data/lib/merb-cache/cache-store/memory.rb +0 -168
- data/lib/merb-cache/merb-cache.rb +0 -165
- data/lib/merb-cache/merbtasks.rb +0 -6
- data/spec/config/database.yml +0 -14
- data/spec/controller.rb +0 -101
- data/spec/log/merb_test.log +0 -433
- data/spec/merb-cache-action_spec.rb +0 -162
- data/spec/merb-cache-fragment_spec.rb +0 -100
- data/spec/merb-cache-page_spec.rb +0 -150
- data/spec/merb-cache_spec.rb +0 -15
- data/spec/views/cache_controller/action1.html.erb +0 -4
- data/spec/views/cache_controller/action2.html.haml +0 -4
@@ -0,0 +1,69 @@
|
|
1
|
+
module Merb::Cache
|
2
|
+
# General purpose store, use for your own
|
3
|
+
# contexts. Since it wraps access to multiple
|
4
|
+
# fundamental stores, it's easy to use
|
5
|
+
# this strategy store with distributed cache
|
6
|
+
# stores like Memcached.
|
7
|
+
class AdhocStore < AbstractStrategyStore
|
8
|
+
class << self
|
9
|
+
alias_method :[], :new
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :stores
|
13
|
+
|
14
|
+
def initialize(*names)
|
15
|
+
@stores = names.map {|n| Merb::Cache[n]}
|
16
|
+
end
|
17
|
+
|
18
|
+
def writable?(key, parameters = {}, conditions = {})
|
19
|
+
@stores.capture_first {|s| s.writable?(key, parameters, conditions)}
|
20
|
+
end
|
21
|
+
|
22
|
+
# gets the data from the store identified by the key & parameters.
|
23
|
+
# return nil if the entry does not exist.
|
24
|
+
def read(key, parameters = {})
|
25
|
+
@stores.capture_first {|s| s.read(key, parameters)}
|
26
|
+
end
|
27
|
+
|
28
|
+
# persists the data so that it can be retrieved by the key & parameters.
|
29
|
+
# returns nil if it is unable to persist the data.
|
30
|
+
# returns true if successful.
|
31
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
32
|
+
@stores.capture_first {|s| s.write(key, data, parameters, conditions)}
|
33
|
+
end
|
34
|
+
|
35
|
+
# persists the data to all context stores.
|
36
|
+
# returns nil if none of the stores were able to persist the data.
|
37
|
+
# returns true if at least one write was successful.
|
38
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
39
|
+
@stores.map {|s| s.write_all(key, data, parameters, conditions)}.all?
|
40
|
+
end
|
41
|
+
|
42
|
+
# tries to read the data from the store. If that fails, it calls
|
43
|
+
# the block parameter and persists the result. If it cannot be fetched,
|
44
|
+
# the block call is returned.
|
45
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
46
|
+
read(key, parameters) ||
|
47
|
+
@stores.capture_first {|s| s.fetch(key, parameters, conditions, &blk)} ||
|
48
|
+
blk.call
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns true/false/nil based on if data identified by the key & parameters
|
52
|
+
# is persisted in the store.
|
53
|
+
def exists?(key, parameters = {})
|
54
|
+
@stores.capture_first {|s| s.exists?(key, parameters)}
|
55
|
+
end
|
56
|
+
|
57
|
+
# deletes the entry for the key & parameter from the store.
|
58
|
+
def delete(key, parameters = {})
|
59
|
+
@stores.map {|s| s.delete(key, parameters)}.any?
|
60
|
+
end
|
61
|
+
|
62
|
+
# deletes all entries for the key & parameters for the store.
|
63
|
+
# considered dangerous because strategy stores which call delete_all!
|
64
|
+
# on their context stores could delete other store's entrees.
|
65
|
+
def delete_all!
|
66
|
+
@stores.map {|s| s.delete_all!}.all?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Pinched from Tobias Luetke's "cacheable" rails plugin (http://github.com/tobi/cacheable/tree/master)
|
2
|
+
require 'zlib'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
module Merb::Cache
|
6
|
+
# Store that compresses cached data using GZip.
|
7
|
+
# Usually wraps other stores and good for caching of
|
8
|
+
# large pages.
|
9
|
+
class GzipStore < AbstractStrategyStore
|
10
|
+
def writable?(key, parameters = {}, conditions = {})
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(key, parameters = {})
|
15
|
+
decompress(@stores.capture_first {|c| c.read(key, parameters)})
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
19
|
+
if writable?(key, parameters, conditions)
|
20
|
+
@stores.capture_first {|c| c.write(key, compress(data), parameters, conditions)}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
25
|
+
if writable?(key, parameters, conditions)
|
26
|
+
@stores.map {|c| c.write_all(key, compress(data), parameters, conditions)}.all?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
31
|
+
wrapper_blk = lambda { compress(blk.call) }
|
32
|
+
decompress(read(key, parameters) || @stores.capture_first {|s| s.fetch(key, parameters, conditions, &wrapper_blk)})
|
33
|
+
end
|
34
|
+
|
35
|
+
def exists?(key, parameters = {})
|
36
|
+
@stores.capture_first {|c| c.exists?(key, parameters)}
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete(key, parameters = {})
|
40
|
+
@stores.map {|c| c.delete(key, parameters)}.any?
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_all!
|
44
|
+
@stores.map {|c| c.delete_all! }.all?
|
45
|
+
end
|
46
|
+
|
47
|
+
def compress(data)
|
48
|
+
return if data.nil?
|
49
|
+
|
50
|
+
output = StringIO.new
|
51
|
+
gz = Zlib::GzipWriter.new(output)
|
52
|
+
gz.write(Marshal.dump(data))
|
53
|
+
gz.close
|
54
|
+
output.string
|
55
|
+
end
|
56
|
+
|
57
|
+
def decompress(data)
|
58
|
+
return if data.nil?
|
59
|
+
|
60
|
+
Marshal.load(Zlib::GzipReader.new(StringIO.new(data)).read)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Merb::Cache
|
2
|
+
# Store well suited for page caching.
|
3
|
+
class PageStore < AbstractStrategyStore
|
4
|
+
def writable?(dispatch, parameters = {}, conditions = {})
|
5
|
+
if Merb::Controller === dispatch && dispatch.request.method == :get &&
|
6
|
+
!dispatch.request.uri.nil? && !dispatch.request.uri.empty? &&
|
7
|
+
!conditions.has_key?(:if) && !conditions.has_key?(:unless) &&
|
8
|
+
query_string_present?(dispatch)
|
9
|
+
@stores.any? {|s| s.writable?(normalize(dispatch), parameters, conditions)}
|
10
|
+
else
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def read(dispatch, parameters = {})
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def write(dispatch, data = nil, parameters = {}, conditions = {})
|
20
|
+
if writable?(dispatch, parameters, conditions)
|
21
|
+
@stores.capture_first {|s| s.write(normalize(dispatch), data || dispatch.body, {}, conditions)}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def write_all(dispatch, data = nil, parameters = {}, conditions = {})
|
26
|
+
if writable?(dispatch, parameters, conditions)
|
27
|
+
@stores.map {|s| s.write_all(normalize(dispatch), data || dispatch.body, {}, conditions)}.all?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch(dispatch, parameters = {}, conditions = {}, &blk)
|
32
|
+
if writable?(dispatch, parameters, conditions)
|
33
|
+
read(dispatch, parameters) || @stores.capture_first {|s| s.fetch(normalize(dispatch), data || dispatch.body, {}, conditions, &blk)}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def exists?(dispatch, parameters = {})
|
38
|
+
if writable?(dispatch, parameters)
|
39
|
+
@stores.capture_first {|s| s.exists?(normalize(dispatch), {})}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(dispatch, parameters = {})
|
44
|
+
if writable?(dispatch, parameters)
|
45
|
+
@stores.map {|s| s.delete(normalize(dispatch), {})}.any?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete_all!
|
50
|
+
@stores.map {|s| s.delete_all!}.all?
|
51
|
+
end
|
52
|
+
|
53
|
+
def normalize(dispatch)
|
54
|
+
key = dispatch.request.uri.split('?').first
|
55
|
+
key << "index" if key =~ /\/$/
|
56
|
+
key << ".#{dispatch.content_type}" unless key =~ /\.\w{2,6}/
|
57
|
+
key
|
58
|
+
end
|
59
|
+
|
60
|
+
def query_string_present?(dispatch)
|
61
|
+
dispatch.request.env["REQUEST_URI"] == dispatch.request.uri
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Merb::Cache
|
4
|
+
# Strategy store that uses SHA1 hex of
|
5
|
+
# base cache key and parameters as
|
6
|
+
# cache key.
|
7
|
+
#
|
8
|
+
# It is good for caching of expensive
|
9
|
+
# search queries that use multiple
|
10
|
+
# parameters passed via query string
|
11
|
+
# of request.
|
12
|
+
class SHA1Store < AbstractStrategyStore
|
13
|
+
def initialize(config = {})
|
14
|
+
super(config)
|
15
|
+
@map = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def writable?(key, parameters = {}, conditions = {})
|
19
|
+
case key
|
20
|
+
when String, Numeric, Symbol
|
21
|
+
@stores.any? {|c| c.writable?(digest(key, parameters), {}, conditions)}
|
22
|
+
else nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def read(key, parameters = {})
|
27
|
+
@stores.capture_first {|c| c.read(digest(key, parameters))}
|
28
|
+
end
|
29
|
+
|
30
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
31
|
+
if writable?(key, parameters, conditions)
|
32
|
+
@stores.capture_first {|c| c.write(digest(key, parameters), data, {}, conditions)}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
37
|
+
if writable?(key, parameters, conditions)
|
38
|
+
@stores.map {|c| c.write_all(digest(key, parameters), data, {}, conditions)}.all?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
43
|
+
read(key, parameters) || (writable?(key, parameters, conditions) && @stores.capture_first {|c| c.fetch(digest(key, parameters), {}, conditions, &blk)})
|
44
|
+
end
|
45
|
+
|
46
|
+
def exists?(key, parameters = {})
|
47
|
+
@stores.capture_first {|c| c.exists?(digest(key, parameters))}
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(key, parameters = {})
|
51
|
+
@stores.map {|c| c.delete(digest(key, parameters))}.any?
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_all!
|
55
|
+
@stores.map {|c| c.delete_all! }.all?
|
56
|
+
end
|
57
|
+
|
58
|
+
def digest(key, parameters = {})
|
59
|
+
@map[[key, parameters]] ||= Digest::SHA1.hexdigest("#{key}#{parameters.to_sha2}")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/merb-cache.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
# make sure we're running inside Merb
|
1
2
|
if defined?(Merb::Plugins)
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
require "merb-cache" / "cache"
|
4
|
+
require "merb-cache" / "core_ext" / "enumerable"
|
5
|
+
require "merb-cache" / "core_ext" / "hash"
|
6
|
+
require "merb-cache" / "merb_ext" / "controller"
|
7
|
+
|
8
|
+
class Merb::Controller
|
9
|
+
include Merb::Cache::CacheMixin
|
8
10
|
end
|
9
|
-
require "merb-cache/merb-cache"
|
10
11
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Merb::Cache do
|
4
|
+
before(:each) do
|
5
|
+
Merb::Cache.stores.clear
|
6
|
+
Thread.current[:'merb-cache'] = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".setup" do
|
10
|
+
it "should have have access to the Merb::Cache.register method from the block" do
|
11
|
+
Merb::Cache.setup do
|
12
|
+
self.respond_to?(:register).should == true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".register" do
|
18
|
+
it "should add the store name and instance to the store hash" do
|
19
|
+
Merb::Cache.stores.should_not have_key(:foo)
|
20
|
+
Merb::Cache.register(:foo, DummyStore)
|
21
|
+
Merb::Cache.stores.should have_key(:foo)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should use :default when no name is supplied" do
|
25
|
+
Merb::Cache.stores.should_not have_key(:default)
|
26
|
+
Merb::Cache.register(DummyStore)
|
27
|
+
Merb::Cache.stores.should have_key(:default)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not allow a store to be redefined" do
|
31
|
+
Merb::Cache.register(DummyStore)
|
32
|
+
lambda do
|
33
|
+
Merb::Cache.register(DummyStore)
|
34
|
+
end.should raise_error(Merb::Cache::StoreExists)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe ".exists?" do
|
39
|
+
it "should return true if a repository is setup" do
|
40
|
+
Merb::Cache.register(DummyStore)
|
41
|
+
Merb::Cache.register(:store_that_exists, DummyStore)
|
42
|
+
Merb::Cache.exists?(:default).should be_true
|
43
|
+
Merb::Cache.exists?(:store_that_exists).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return false if a repository is not setup" do
|
47
|
+
Merb::Cache.exists?(:not_here).should be_false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".[]" do
|
52
|
+
it "should clone the stores so to keep them threadsafe" do
|
53
|
+
Merb::Cache.register(DummyStore)
|
54
|
+
Merb::Cache[:default].should_not be_nil
|
55
|
+
Merb::Cache[:default].should_not == Merb::Cache.stores[:default]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should cache the thread local stores in Thread.current" do
|
59
|
+
Merb::Cache.register(DummyStore)
|
60
|
+
Thread.current[:'merb-cache'].should be_nil
|
61
|
+
Merb::Cache[:default]
|
62
|
+
Thread.current[:'merb-cache'].should_not be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should create an adhoc store if multiple store names are supplied" do
|
66
|
+
Merb::Cache.register(DummyStore)
|
67
|
+
Merb::Cache.register(:dummy, DummyStore)
|
68
|
+
Merb::Cache[:default, :dummy].class.should == Merb::Cache::AdhocStore
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should let you create new stores after accessing the old ones" do
|
72
|
+
Merb::Cache.register(DummyStore)
|
73
|
+
Merb::Cache.register(:one, DummyStore)
|
74
|
+
Merb::Cache[:default].should_not be_nil
|
75
|
+
Merb::Cache[:one].should_not be_nil
|
76
|
+
Merb::Cache.register(:two, DummyStore)
|
77
|
+
Merb::Cache[:two].should_not be_nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should raise an error if the cache has not been setup" do
|
81
|
+
Merb::Cache.register(DummyStore)
|
82
|
+
Merb::Cache[:default].should_not be_nil
|
83
|
+
lambda do
|
84
|
+
Merb::Cache[:does_not_exist]
|
85
|
+
end.should raise_error(Merb::Cache::StoreNotFound)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Enumerable do
|
4
|
+
describe "#capture_first" do
|
5
|
+
it "should return the result of the first block call that is non-nil, not the item sent to the block" do
|
6
|
+
[1, 2, 3].capture_first {|i| i ** i if i % 2 == 0}.should == 4
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return nil if all block calls are nil" do
|
10
|
+
[1, 2, 3].capture_first {|i| nil }.should be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should stop calling the block once a block evaluates to non-nil" do
|
14
|
+
lambda {
|
15
|
+
[1, 2, 3].capture_first do |i|
|
16
|
+
raise "#{i} is divisible by 3!" if i % 3 == 0
|
17
|
+
i ** i if i % 2 == 0
|
18
|
+
end
|
19
|
+
}.should_not raise_error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Hash do
|
4
|
+
|
5
|
+
describe "to_sha1" do
|
6
|
+
before(:each) do
|
7
|
+
@params = {:id => 1, :string => "string", :symbol => :symbol}
|
8
|
+
end
|
9
|
+
|
10
|
+
it{@params.should respond_to(:to_sha2)}
|
11
|
+
|
12
|
+
it "should encode the hash by alphabetic key" do
|
13
|
+
string = ""
|
14
|
+
@params.keys.sort_by{|k| k.to_s}.each{|k| string << @params[k].to_s}
|
15
|
+
digest = Digest::SHA2.hexdigest(string)
|
16
|
+
@params.to_sha2.should == digest
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,284 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Merb::Cache::CacheMixin do
|
4
|
+
before(:all) do
|
5
|
+
Merb::Cache.stores.clear
|
6
|
+
Thread.current[:'merb-cache'] = nil
|
7
|
+
|
8
|
+
Merb::Cache.register(:default, DummyStore)
|
9
|
+
@dummy = Merb::Cache[:default]
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".cache!" do
|
13
|
+
it "should add a before filter for all actions" do
|
14
|
+
class CacheAllActionsBeforeCallController < Merb::Controller
|
15
|
+
self.should_receive(:before).with(:_cache_before, :with =>{})
|
16
|
+
|
17
|
+
cache!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should add an after filter for all actions" do
|
22
|
+
class CacheAllActionsAfterCallController < Merb::Controller
|
23
|
+
self.should_receive(:after).with(:_cache_after, :with =>{})
|
24
|
+
|
25
|
+
cache!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".cache" do
|
31
|
+
it "should call .cache_action with each method symbol" do
|
32
|
+
class CacheActionsCallController < Merb::Controller
|
33
|
+
actions = [:first, :second, :third]
|
34
|
+
|
35
|
+
actions.each do |action|
|
36
|
+
self.should_receive(:cache_action).with(action)
|
37
|
+
end
|
38
|
+
|
39
|
+
cache *actions
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should call .cache_action with the method symbol and conditions hash" do
|
44
|
+
class CacheActionConditionsController < Merb::Controller
|
45
|
+
self.should_receive(:cache_action).with(:action, :conditions => :hash)
|
46
|
+
|
47
|
+
cache :action, :conditions => :hash
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe ".cache_action" do
|
53
|
+
it "should add the before filter for only the action" do
|
54
|
+
class CacheActionBeforeController < Merb::Controller
|
55
|
+
self.should_receive(:before).with("_cache_action_before", :with => [{}], :only => :action)
|
56
|
+
|
57
|
+
cache_action(:action)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should add the after filter for only the action" do
|
62
|
+
class CacheActionAfterController < Merb::Controller
|
63
|
+
self.should_receive(:after).with("_cache_action_after", :with => [{}], :only => :action)
|
64
|
+
|
65
|
+
cache_action(:action)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
before(:each) do
|
71
|
+
class TestController < Merb::Controller; end
|
72
|
+
@controller = TestController.new(fake_request)
|
73
|
+
@controller.stub!(:action_name).and_return :index
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#_lookup_store" do
|
77
|
+
it "should use the :store entry from the conditions hash" do
|
78
|
+
@controller._lookup_store(:store => :foo_store).should == :foo_store
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should use the :stores entry from the conditions hash" do
|
82
|
+
@controller._lookup_store(:stores => [:foo_store, :bar_store]).should == [:foo_store, :bar_store]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should default store name if none is supplied in the conditions hash" do
|
86
|
+
@controller._lookup_store.should == Merb::Cache.default_store_name
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should request the default_cache_store" do
|
90
|
+
@controller.should_receive(:default_cache_store).and_return(Merb::Cache.default_store_name)
|
91
|
+
@controller._lookup_store
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#_parameters_and_conditions" do
|
96
|
+
it "should remove the :params entry from the conditions hash" do
|
97
|
+
@controller._parameters_and_conditions(:params => [:foo, :bar]).last.should_not include(:params)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should remove the :store entry from the conditions hash" do
|
101
|
+
@controller._parameters_and_conditions(:store => :foo_store).last.should_not include(:store)
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should remove the :stores entry from the conditions hash" do
|
105
|
+
@controller._parameters_and_conditions(:stores => [:foo_store, :bar_store]).last.should_not include(:stores)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should keep an :expires_in entry in the conditions hash" do
|
109
|
+
@controller._parameters_and_conditions(:expire_in => 10).last.should include(:expire_in)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should move the :params entry to the parameters array" do
|
113
|
+
@controller._parameters_and_conditions(:params => :foo).first.should include(:foo)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#skip_cache!" do
|
118
|
+
it "should set @_skip_cache = true" do
|
119
|
+
lambda { @controller.skip_cache! }.should change { @controller.instance_variable_get(:@_skip_cache) }.to(true)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "force_cache!" do
|
124
|
+
class AController < Merb::Controller
|
125
|
+
|
126
|
+
cache :start
|
127
|
+
|
128
|
+
def start
|
129
|
+
"START"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
before(:each) do
|
134
|
+
@mock_store = mock("store", :null_object => true)
|
135
|
+
Merb::Cache.stub!(:[]).and_return(@mock_store)
|
136
|
+
@mock_store.stub!(:read).and_return("CACHED")
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should try to hit the cache if force_cache is not set" do
|
140
|
+
@mock_store.should_receive(:read).and_return("CACHED")
|
141
|
+
controller = dispatch_to(AController, :start)
|
142
|
+
controller.body.should == "CACHED"
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should not try to hit the cache if force_cache is set" do
|
146
|
+
@mock_store.should_not_receive(:read)
|
147
|
+
controller = dispatch_to(AController, :start){|c| c.force_cache!}
|
148
|
+
controller.body.should == "START"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "eager_cache (Instance Method)" do
|
153
|
+
class HasRun
|
154
|
+
cattr_accessor :has_run
|
155
|
+
end
|
156
|
+
|
157
|
+
def dispatch_and_wait(*args)
|
158
|
+
@controller = nil
|
159
|
+
Timeout.timeout(2) do
|
160
|
+
until HasRun.has_run do
|
161
|
+
@controller ||= dispatch_to(*args){ |c|
|
162
|
+
yield c if block_given?
|
163
|
+
}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
@controller
|
167
|
+
end
|
168
|
+
|
169
|
+
class MyController < Merb::Controller
|
170
|
+
after nil, :only => :start do
|
171
|
+
eager_cache :stop
|
172
|
+
end
|
173
|
+
|
174
|
+
def start
|
175
|
+
"START"
|
176
|
+
end
|
177
|
+
|
178
|
+
def stop
|
179
|
+
"STOP"
|
180
|
+
end
|
181
|
+
|
182
|
+
def inline_call
|
183
|
+
eager_cache :stop
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
before(:each) do
|
188
|
+
HasRun.has_run = false
|
189
|
+
end
|
190
|
+
|
191
|
+
after(:each) do
|
192
|
+
HasRun.has_run = false
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should run the stop action from a new instance as an after filter" do
|
196
|
+
new_controller = MyController.new(fake_request)
|
197
|
+
dispatch_and_wait(MyController, :start) do |c|
|
198
|
+
MyController.should_receive(:new).and_return(new_controller)
|
199
|
+
new_controller.should_receive(:stop).and_return(HasRun.has_run = true)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should run the stop action from a new instance called inline" do
|
204
|
+
new_controller = MyController.new(fake_request)
|
205
|
+
dispatch_and_wait(MyController, :inline_call) do |c|
|
206
|
+
MyController.should_receive(:new).and_return(new_controller)
|
207
|
+
new_controller.should_receive(:stop).and_return(HasRun.has_run = true)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should not run the stop action if the _set_skip_cache is set to true" do
|
212
|
+
new_controller = MyController.new(fake_request)
|
213
|
+
dispatch_and_wait(MyController, :start) do |c|
|
214
|
+
MyController.stub!(:new).and_return(new_controller)
|
215
|
+
c.skip_cache!
|
216
|
+
new_controller.should_not_receive(:stop)
|
217
|
+
HasRun.has_run = true
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should set the cache to be forced" do
|
222
|
+
new_controller = MyController.new(fake_request)
|
223
|
+
dispatch_and_wait(MyController, :start) do |c|
|
224
|
+
MyController.should_receive(:new).and_return(new_controller)
|
225
|
+
new_controller.should_receive(:force_cache!)
|
226
|
+
new_controller.should_receive(:stop).and_return(HasRun.has_run = true)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "#fetch_partial" do
|
232
|
+
it "should pass the template argument to the partial method" do
|
233
|
+
new_controller = MyController.new(fake_request)
|
234
|
+
new_controller.should_receive(:partial).with(:foo, {})
|
235
|
+
new_controller.stub!(:concat)
|
236
|
+
|
237
|
+
new_controller.fetch_partial(:foo)
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should pass the options to the partial method " do
|
241
|
+
new_controller = MyController.new(fake_request)
|
242
|
+
new_controller.should_receive(:partial).with(:foo, :bar => :baz)
|
243
|
+
new_controller.stub!(:concat)
|
244
|
+
|
245
|
+
new_controller.fetch_partial(:foo, :bar => :baz)
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should contain only alpha-numeric characters in the template key" do
|
249
|
+
new_controller = MyController.new(fake_request)
|
250
|
+
new_controller.stub!(:partial)
|
251
|
+
new_controller.stub!(:concat)
|
252
|
+
|
253
|
+
@dummy.should_receive(:fetch).and_return do |template_key, opts, conditions, block|
|
254
|
+
template_key.should =~ /^(?:[a-zA-Z0-9_\/\.-])+/
|
255
|
+
end
|
256
|
+
|
257
|
+
new_controller.fetch_partial('path/to/foo')
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe "#fetch_fragment" do
|
262
|
+
it "should include the filename that defines the fragment proc in the fragment key" do
|
263
|
+
new_controller = MyController.new(fake_request)
|
264
|
+
new_controller.stub!(:concat)
|
265
|
+
|
266
|
+
@dummy.should_receive(:fetch).and_return do |fragment_key, opts, conditions, block|
|
267
|
+
fragment_key.should include(__FILE__)
|
268
|
+
end
|
269
|
+
|
270
|
+
new_controller.fetch_fragment {}
|
271
|
+
end
|
272
|
+
|
273
|
+
it "should include the line number that defines the fragment proc in the fragment key" do
|
274
|
+
new_controller = MyController.new(fake_request)
|
275
|
+
new_controller.stub!(:concat)
|
276
|
+
|
277
|
+
@dummy.should_receive(:fetch).and_return do |fragment_key, opts, conditions, block|
|
278
|
+
fragment_key.should =~ %r{[\d+]}
|
279
|
+
end
|
280
|
+
|
281
|
+
new_controller.fetch_fragment {}
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|