merb-cache 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|