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.
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