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.
Files changed (55) hide show
  1. data/LICENSE +2 -2
  2. data/README +207 -143
  3. data/Rakefile +55 -10
  4. data/TODO +0 -2
  5. data/lib/merb-cache/cache.rb +84 -0
  6. data/lib/merb-cache/core_ext/enumerable.rb +9 -0
  7. data/lib/merb-cache/core_ext/hash.rb +20 -0
  8. data/lib/merb-cache/merb_ext/controller.rb +167 -0
  9. data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
  10. data/lib/merb-cache/stores/fundamental/file_store.rb +112 -0
  11. data/lib/merb-cache/stores/fundamental/memcached_store.rb +112 -0
  12. data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +123 -0
  13. data/lib/merb-cache/stores/strategy/action_store.rb +56 -0
  14. data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
  15. data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
  16. data/lib/merb-cache/stores/strategy/page_store.rb +64 -0
  17. data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
  18. data/lib/merb-cache.rb +8 -7
  19. data/spec/merb-cache/cache_spec.rb +88 -0
  20. data/spec/merb-cache/core_ext/enumerable_spec.rb +22 -0
  21. data/spec/merb-cache/core_ext/hash_spec.rb +20 -0
  22. data/spec/merb-cache/merb_ext/controller_spec.rb +284 -0
  23. data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +166 -0
  24. data/spec/merb-cache/stores/fundamental/file_store_spec.rb +186 -0
  25. data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +243 -0
  26. data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
  27. data/spec/merb-cache/stores/strategy/action_store_spec.rb +189 -0
  28. data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +225 -0
  29. data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +51 -0
  30. data/spec/merb-cache/stores/strategy/page_store_spec.rb +111 -0
  31. data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +75 -0
  32. data/spec/spec_helper.rb +69 -72
  33. metadata +42 -31
  34. data/lib/merb-cache/cache-action.rb +0 -144
  35. data/lib/merb-cache/cache-fragment.rb +0 -95
  36. data/lib/merb-cache/cache-page.rb +0 -203
  37. data/lib/merb-cache/cache-store/database-activerecord.rb +0 -88
  38. data/lib/merb-cache/cache-store/database-datamapper.rb +0 -79
  39. data/lib/merb-cache/cache-store/database-sequel.rb +0 -78
  40. data/lib/merb-cache/cache-store/database.rb +0 -144
  41. data/lib/merb-cache/cache-store/dummy.rb +0 -106
  42. data/lib/merb-cache/cache-store/file.rb +0 -194
  43. data/lib/merb-cache/cache-store/memcache.rb +0 -199
  44. data/lib/merb-cache/cache-store/memory.rb +0 -168
  45. data/lib/merb-cache/merb-cache.rb +0 -165
  46. data/lib/merb-cache/merbtasks.rb +0 -6
  47. data/spec/config/database.yml +0 -14
  48. data/spec/controller.rb +0 -101
  49. data/spec/log/merb_test.log +0 -433
  50. data/spec/merb-cache-action_spec.rb +0 -162
  51. data/spec/merb-cache-fragment_spec.rb +0 -100
  52. data/spec/merb-cache-page_spec.rb +0 -150
  53. data/spec/merb-cache_spec.rb +0 -15
  54. data/spec/views/cache_controller/action1.html.erb +0 -4
  55. 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
- Merb::Plugins.add_rakefiles "merb-cache/merbtasks"
3
- unless 1.respond_to? :minutes
4
- class Numeric
5
- def minutes; self * 60; end
6
- def from_now(now = Time.now); now + self; end
7
- end
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