benschwarz-merb-cache 1.0.0

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 (37) hide show
  1. data/LICENSE +20 -0
  2. data/README +224 -0
  3. data/Rakefile +17 -0
  4. data/lib/merb-cache.rb +15 -0
  5. data/lib/merb-cache/cache.rb +91 -0
  6. data/lib/merb-cache/cache_request.rb +48 -0
  7. data/lib/merb-cache/core_ext/enumerable.rb +9 -0
  8. data/lib/merb-cache/core_ext/hash.rb +21 -0
  9. data/lib/merb-cache/merb_ext/controller/class_methods.rb +244 -0
  10. data/lib/merb-cache/merb_ext/controller/instance_methods.rb +163 -0
  11. data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
  12. data/lib/merb-cache/stores/fundamental/file_store.rb +113 -0
  13. data/lib/merb-cache/stores/fundamental/memcached_store.rb +110 -0
  14. data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +119 -0
  15. data/lib/merb-cache/stores/strategy/action_store.rb +61 -0
  16. data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
  17. data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
  18. data/lib/merb-cache/stores/strategy/mintcache_store.rb +75 -0
  19. data/lib/merb-cache/stores/strategy/page_store.rb +68 -0
  20. data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
  21. data/spec/merb-cache/cache_request_spec.rb +78 -0
  22. data/spec/merb-cache/cache_spec.rb +88 -0
  23. data/spec/merb-cache/core_ext/enumerable_spec.rb +26 -0
  24. data/spec/merb-cache/core_ext/hash_spec.rb +51 -0
  25. data/spec/merb-cache/merb_ext/controller_spec.rb +5 -0
  26. data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +118 -0
  27. data/spec/merb-cache/stores/fundamental/file_store_spec.rb +205 -0
  28. data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +258 -0
  29. data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
  30. data/spec/merb-cache/stores/strategy/action_store_spec.rb +208 -0
  31. data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +227 -0
  32. data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +68 -0
  33. data/spec/merb-cache/stores/strategy/mintcache_store_spec.rb +59 -0
  34. data/spec/merb-cache/stores/strategy/page_store_spec.rb +146 -0
  35. data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +84 -0
  36. data/spec/spec_helper.rb +95 -0
  37. metadata +112 -0
@@ -0,0 +1,78 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require File.dirname(__FILE__) + '/../fundamental/abstract_store_spec'
3
+
4
+
5
+ describe Merb::Cache::AbstractStrategyStore do
6
+ describe 'all strategy stores', :shared => true do
7
+ it_should_behave_like 'all stores'
8
+
9
+ before(:each) do
10
+ Merb::Cache.stores.clear
11
+ Thread.current[:'merb-cache'] = nil
12
+ end
13
+
14
+ describe "contextualizing method", :shared => true do
15
+ it "should return a subclass of itself" do
16
+ subclass = @klass.[](Class.new(Merb::Cache::AbstractStore))
17
+ subclass.superclass.should == @klass
18
+ end
19
+
20
+ it "should set the contextualized_stores attributes" do
21
+ subclass = @klass.[](context_class = Class.new(Merb::Cache::AbstractStore))
22
+ subclass.contextualized_stores.first.should == context_class
23
+ end
24
+ end
25
+
26
+ describe ".contextualize" do
27
+ it_should_behave_like "contextualizing method"
28
+ end
29
+
30
+ describe ".[]" do
31
+ it_should_behave_like "contextualizing method"
32
+ end
33
+
34
+ describe "#initialize" do
35
+ it "should create an instance of the any context classes" do
36
+ subclass = @klass.[](context_class = Class.new(Merb::Cache::AbstractStore))
37
+ instance = subclass.new({})
38
+ instance.stores.first.class.superclass.should == Merb::Cache::AbstractStore
39
+ end
40
+
41
+ it "should lookup the instance of any context names" do
42
+ Merb::Cache.register(:foo, Class.new(Merb::Cache::AbstractStore))
43
+ subclass = @klass.[](:foo)
44
+ Merb::Cache.should_receive(:[]).with(:foo)
45
+ subclass.new({})
46
+ end
47
+ end
48
+
49
+ describe "#clone" do
50
+ it "should clone each context instance" do
51
+ subclass = @klass.[](context_class = Class.new(Merb::Cache::AbstractStore))
52
+ instance = mock(:instance)
53
+ context_class.should_receive(:new).and_return(instance)
54
+ instance.should_receive(:clone)
55
+
56
+ subclass.new.clone
57
+ end
58
+ end
59
+
60
+ describe "#write_all" do
61
+ it "should not raise a NotImplementedError error" do
62
+ lambda { @store.write_all('foo', 'bar') }.should_not raise_error(NotImplementedError)
63
+ end
64
+
65
+ it "should accept a string key" do
66
+ @store.write_all('foo', 'bar')
67
+ end
68
+
69
+ it "should accept a symbol as a key" do
70
+ @store.write_all(:foo, :bar)
71
+ end
72
+
73
+ it "should accept parameters and conditions" do
74
+ @store.write_all('foo', 'bar', {:params => :hash}, :conditions => :hash)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,208 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require File.dirname(__FILE__) + '/abstract_strategy_store_spec'
3
+
4
+ describe Merb::Cache::ActionStore do
5
+ it_should_behave_like 'all strategy stores'
6
+
7
+ # this could be cleaned up a bit
8
+ before(:each) do
9
+ Merb::Cache.stores.clear
10
+ Thread.current[:'merb-cache'] = nil
11
+
12
+ @klass = Merb::Cache::ActionStore[:dummy]
13
+ Merb::Cache.register(:dummy, DummyStore)
14
+ Merb::Cache.register(:default, @klass)
15
+
16
+ @store = Merb::Cache[:default]
17
+ @dummy = @store.stores.first
18
+
19
+ class TestController < Merb::Controller; def action; end; end
20
+ @controller = TestController.new(fake_request)
21
+ @controller.stub!(:action_name).and_return :action
22
+ @controller.stub!(:body).and_return 'body'
23
+ end
24
+
25
+ describe "#writable?" do
26
+ it "should be false if the key argument is not an instance of a controller" do
27
+ @store.writable?(:foo).should be_false
28
+ end
29
+
30
+ it "should be false if the fundamental store cannot write the normalized key" do
31
+ @store.stores.first.should_receive(:writable?).and_return false
32
+
33
+ @store.writable?(@controller).should be_false
34
+ end
35
+
36
+ it "should be true if the key is a controller instance and the fundamental store is writable" do
37
+ @store.writable?(@controller).should be_true
38
+ end
39
+ end
40
+
41
+ describe "#read" do
42
+ it "should pass the normalized dispatch as the key to the context cache" do
43
+ @dummy.should_receive(:read).with("TestController#action", {})
44
+
45
+ @store.read(@controller)
46
+ end
47
+ end
48
+
49
+ describe "#write" do
50
+ it "should pass the normalized dispatch as the key to the fundamental store" do
51
+ @dummy.should_receive(:write).with("TestController#action", @controller.body, {}, {})
52
+
53
+ @store.write(@controller)
54
+ end
55
+
56
+ it "should not store to the context cache if the dispatch is not storable" do
57
+ @dummy.should_not_receive(:write)
58
+
59
+ @store.write(:foo).should be_false
60
+ end
61
+
62
+ it "should use the controller instance's body as the data" do
63
+ @controller.should_receive(:body)
64
+
65
+ @store.write(@controller)
66
+ end
67
+
68
+ it "should write" do
69
+ @store.write(@controller).should be_true
70
+ end
71
+ end
72
+
73
+ describe "#write_all" do
74
+ it "should pass the normalized dispatch as the key to the fundamental store" do
75
+ @dummy.should_receive(:write_all).with("TestController#action", @controller.body, {}, {})
76
+
77
+ @store.write_all(@controller)
78
+ end
79
+
80
+ it "should not store to the context cache if the dispatch is not storable" do
81
+ @dummy.should_not_receive(:write_all)
82
+
83
+ @store.write_all(:foo).should be_false
84
+ end
85
+
86
+ it "should use the controller instance's body as the data" do
87
+ @controller.should_receive(:body)
88
+
89
+ @store.write_all(@controller)
90
+ end
91
+ end
92
+
93
+ describe "#exists?" do
94
+ it "should return a boolean" do
95
+ @store.write(@controller)
96
+ @store.exists?(@controller).should be_true
97
+ end
98
+ end
99
+
100
+ describe "examples" do
101
+ class MLBSchedule < Merb::Controller
102
+ cache :index
103
+
104
+ def index
105
+ "MLBSchedule index"
106
+ end
107
+ end
108
+
109
+ class MLBScores < Merb::Controller
110
+ cache :index, :show
111
+ cache :overview
112
+ cache :short, :params => :page
113
+ cache :stats, :params => [:start_date, :end_date]
114
+ cache :ticker, :expire_in => 10
115
+
116
+ eager_cache :index, [MLBSchedule, :index]
117
+ eager_cache [:overview, :show], :index
118
+ eager_cache(:short, :params => :page) do |params, env|
119
+ {:params => params.merge(:page => (params[:page].to_i + 1).to_s)}
120
+ end
121
+
122
+ def index
123
+ "MLBScores index"
124
+ end
125
+
126
+ def show(team)
127
+ "MLBScores show(#{team})"
128
+ end
129
+
130
+ def overview(team = :all)
131
+ "MLBScores overview(#{team})"
132
+ end
133
+
134
+ def short(team = :all)
135
+ "MLBScores short(#{team})[#{params[:page]}]"
136
+ end
137
+
138
+ def stats(start_date, end_date, team = :all)
139
+ "MLBScores stats(#{team}, #{start_date}, #{end_date})"
140
+ end
141
+
142
+ def ticker
143
+ "MLBScores ticker"
144
+ end
145
+ end
146
+
147
+ it "should cache the index action on the first request" do
148
+ dispatch_to(MLBScores, :index)
149
+
150
+ @dummy.data("MLBScores#index").should == "MLBScores index"
151
+ end
152
+
153
+ it "should cache the show action by the team parameter using the action arguments" do
154
+ dispatch_to(MLBScores, :show, :team => :redsox)
155
+
156
+ @dummy.data("MLBScores#show", :team => 'redsox').should == "MLBScores show(redsox)"
157
+ end
158
+
159
+ it "should cache the overview action by the default parameter if none is given" do
160
+ dispatch_to(MLBScores, :overview)
161
+
162
+ @dummy.data("MLBScores#overview", :team => :all).should == "MLBScores overview(all)"
163
+ end
164
+
165
+ it "should cache the short action by the team & page parameters" do
166
+ dispatch_to(MLBScores, :short, :team => :bosux, :page => 4)
167
+
168
+ @dummy.data("MLBScores#short", :team => 'bosux', :page => '4').should == "MLBScores short(bosux)[4]"
169
+ end
170
+
171
+ it "should cache the stats action by team, start_date & end_date parameters" do
172
+ start_date, end_date = Time.today.to_s, Time.now.to_s
173
+ dispatch_to(MLBScores, :stats, :start_date => start_date, :end_date => end_date)
174
+
175
+ @dummy.data("MLBScores#stats", :team => :all, :start_date => start_date, :end_date => end_date).should == "MLBScores stats(all, #{start_date}, #{end_date})"
176
+ end
177
+
178
+ it "should cache the ticker action with an expire_in condition" do
179
+ dispatch_to(MLBScores, :ticker)
180
+
181
+ @dummy.conditions("MLBScores#ticker")[:expire_in].should == 10
182
+ end
183
+
184
+ it "should eager cache MLBSchedule#index after a request to MLBScores#index" do
185
+ dispatch_to(MLBScores, :index)
186
+
187
+ @dummy.data("MLBSchedule#index").should == "MLBSchedule index"
188
+ end
189
+
190
+ it "should eager cache :index after a request to :overview" do
191
+ dispatch_to(MLBScores, :overview)
192
+
193
+ @dummy.data("MLBScores#index").should == "MLBScores index"
194
+ end
195
+
196
+ it "should eager cache :index after a request to :show" do
197
+ dispatch_to(MLBScores, :show, :team => :redsox)
198
+
199
+ @dummy.data("MLBScores#index").should == "MLBScores index"
200
+ end
201
+
202
+ it "should eager cache the next :short page" do
203
+ dispatch_to(MLBScores, :short, :team => :bosux, :page => 4)
204
+
205
+ @dummy.data("MLBScores#short", :team => 'bosux', :page => '5').should == "MLBScores short(bosux)[5]"
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,227 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require File.dirname(__FILE__) + '/abstract_strategy_store_spec'
3
+
4
+ describe Merb::Cache::AdhocStore do
5
+ it_should_behave_like 'all stores'
6
+
7
+ before(:each) do
8
+ Merb::Cache.stores.clear
9
+ Thread.current[:'merb-cache'] = nil
10
+ Merb::Cache.register(:dummy, DummyStore)
11
+ @store = Merb::Cache::AdhocStore[:dummy]
12
+ end
13
+
14
+ describe "#initialize" do
15
+ it "should lookup all store names" do
16
+ names = [:first, :second, :third]
17
+ names.each {|n| Merb::Cache.should_receive(:[]).with(n)}
18
+
19
+ Merb::Cache::AdhocStore[*names]
20
+ end
21
+ end
22
+
23
+ describe "#writable?" do
24
+ it "should return the first non-nil result of a writeable store" do
25
+ unwritable, writable = mock(:unwritable_store), mock(:writable_store)
26
+ unwritable.should_receive(:writable?).and_return nil
27
+ writable.should_receive(:writable?).and_return true
28
+
29
+ adhoc = Merb::Cache::AdhocStore.new
30
+ adhoc.stores = [unwritable, writable]
31
+ adhoc.writable?(:foo).should be_true
32
+ end
33
+
34
+ it "should stop calling writable after the first non-nil result" do
35
+ unwritable, writable, unused = mock(:unwritable_store), mock(:writable_store), mock(:unused_store)
36
+ unwritable.stub!(:writable?).and_return nil
37
+ writable.should_receive(:writable?).and_return true
38
+ unused.should_not_receive(:writable?)
39
+
40
+ adhoc = Merb::Cache::AdhocStore.new
41
+ adhoc.stores = [unwritable, writable, unused]
42
+ adhoc.writable?(:foo).should be_true
43
+ end
44
+
45
+ it "should return nil if none of the stores are writable" do
46
+ unwritable = mock(:unwritable_store)
47
+ unwritable.should_receive(:writable?).exactly(3).times.and_return(nil)
48
+
49
+ adhoc = Merb::Cache::AdhocStore.new
50
+ adhoc.stores = [unwritable, unwritable, unwritable]
51
+ adhoc.writable?(:foo).should be_nil
52
+ end
53
+ end
54
+
55
+ describe "#write" do
56
+ it "should write"
57
+
58
+ it "should return the first non-nil result of a writeable store" do
59
+ unwritable, writable = mock(:unwritable_store), mock(:writable_store)
60
+ unwritable.should_receive(:write).and_return nil
61
+ writable.should_receive(:write).and_return true
62
+
63
+ adhoc = Merb::Cache::AdhocStore.new
64
+ adhoc.stores = [unwritable, writable]
65
+ adhoc.write(:foo).should be_true
66
+ end
67
+
68
+ it "should stop calling writable after the first non-nil result" do
69
+ unwritable, writable, unused = mock(:unwritable_store), mock(:writable_store), mock(:unused_store)
70
+ unwritable.stub!(:write).and_return nil
71
+ writable.should_receive(:write).and_return true
72
+ unused.should_not_receive(:write)
73
+
74
+ adhoc = Merb::Cache::AdhocStore.new
75
+ adhoc.stores = [unwritable, writable, unused]
76
+ adhoc.write(:foo).should be_true
77
+ end
78
+
79
+ it "should return nil if none of the stores are writable" do
80
+ unwritable = mock(:unwritable_store)
81
+ unwritable.should_receive(:write).exactly(3).times.and_return(nil)
82
+
83
+ adhoc = Merb::Cache::AdhocStore.new
84
+ adhoc.stores = [unwritable, unwritable, unwritable]
85
+ adhoc.write(:foo).should be_nil
86
+ end
87
+ end
88
+
89
+ describe "#write_all" do
90
+ it "should return false if a store returns nil for write_all" do
91
+ unwritable, writable = mock(:unwritable_store), mock(:writable_store)
92
+ unwritable.should_receive(:write_all).and_return nil
93
+ writable.should_receive(:write_all).and_return "bar"
94
+
95
+ adhoc = Merb::Cache::AdhocStore.new
96
+ adhoc.stores = [unwritable, writable]
97
+ adhoc.write_all(:foo).should be_false
98
+ end
99
+
100
+ it "should always call write_all on every store" do
101
+ unwritable, writable, used = mock(:unwritable_store), mock(:writable_store), mock(:used_store)
102
+ unwritable.stub!(:write_all).and_return nil
103
+ writable.should_receive(:write_all).and_return true
104
+ used.should_receive(:write_all).and_return true
105
+
106
+ adhoc = Merb::Cache::AdhocStore.new
107
+ adhoc.stores = [unwritable, writable, used]
108
+ adhoc.write_all(:foo).should be_false
109
+ end
110
+ end
111
+
112
+ describe "#fetch" do
113
+ it "should return a call to read if it is non-nil" do
114
+ adhoc = Merb::Cache::AdhocStore.new
115
+ adhoc.should_receive(:read).and_return "bar"
116
+ adhoc.fetch(:foo).should == "bar"
117
+ end
118
+
119
+ it "should return the first non-nil result of a fetchable store" do
120
+ unfetchable, fetchable = mock(:unfetchable_store), mock(:fetchable_store)
121
+ unfetchable.should_receive(:fetch).and_return nil
122
+ fetchable.should_receive(:fetch).and_return true
123
+
124
+ adhoc = Merb::Cache::AdhocStore.new
125
+ adhoc.stores = [unfetchable, fetchable]
126
+ adhoc.should_receive(:read).and_return nil
127
+ adhoc.fetch(:foo).should be_true
128
+ end
129
+
130
+ it "should return the value of the block if none of the stores are fetchable" do
131
+ adhoc = Merb::Cache::AdhocStore.new
132
+ adhoc.fetch(:foo) {
133
+ 'foo'
134
+
135
+ }.should == 'foo'
136
+ end
137
+
138
+ it "should stop calling fetch after the first non-nil result" do
139
+ unfetchable, fetchable, unused = mock(:unwritable_store), mock(:writable_store), mock(:unused_store)
140
+ unfetchable.stub!(:fetch).and_return nil
141
+ fetchable.should_receive(:fetch).and_return true
142
+ unused.should_not_receive(:fetch)
143
+
144
+ adhoc = Merb::Cache::AdhocStore.new
145
+ adhoc.stores = [unfetchable, fetchable, unused]
146
+ adhoc.should_receive(:read).and_return nil
147
+ adhoc.fetch(:foo).should be_true
148
+ end
149
+ end
150
+
151
+ describe "#exists?" do
152
+ it "should return the first non-nil result of a readable store" do
153
+ unreadable, readable = mock(:unreadable_store), mock(:readable_store)
154
+ unreadable.should_receive(:exists?).and_return nil
155
+ readable.should_receive(:exists?).and_return true
156
+
157
+ adhoc = Merb::Cache::AdhocStore.new
158
+ adhoc.stores = [unreadable, readable]
159
+ adhoc.exists?(:foo).should be_true
160
+ end
161
+
162
+ it "should stop calling readable after the first non-nil result" do
163
+ unreadable, readable, unused = mock(:unreadable_store), mock(:readable_store), mock(:unused_store)
164
+ unreadable.stub!(:exists?).and_return nil
165
+ readable.should_receive(:exists?).and_return true
166
+ unused.should_not_receive(:exists?)
167
+
168
+ adhoc = Merb::Cache::AdhocStore.new
169
+ adhoc.stores = [unreadable, readable, unused]
170
+ adhoc.exists?(:foo).should be_true
171
+ end
172
+
173
+ it "should return nil if none of the stores are readable" do
174
+ unreadable = mock(:unreadable_store)
175
+ unreadable.should_receive(:exists?).exactly(3).times.and_return(nil)
176
+
177
+ adhoc = Merb::Cache::AdhocStore.new
178
+ adhoc.stores = [unreadable, unreadable, unreadable]
179
+ adhoc.exists?(:foo).should be_false
180
+ end
181
+ end
182
+
183
+ describe "#delete" do
184
+ it "should return false if all stores returns nil for delete" do
185
+ undeletable = mock(:undeletable_store)
186
+ undeletable.should_receive(:delete).and_return nil
187
+
188
+ adhoc = Merb::Cache::AdhocStore.new
189
+ adhoc.stores = [undeletable]
190
+ adhoc.delete(:foo).should be_false
191
+ end
192
+
193
+ it "should always call delete on every store" do
194
+ undeletable, deletable, used = mock(:undeletable_store), mock(:deletable_store), mock(:used_store)
195
+ undeletable.stub!(:delete).and_return nil
196
+ deletable.should_receive(:delete).and_return true
197
+ used.should_receive(:delete).and_return true
198
+
199
+ adhoc = Merb::Cache::AdhocStore.new
200
+ adhoc.stores = [undeletable, deletable, used]
201
+ adhoc.delete(:foo).should be_true
202
+ end
203
+ end
204
+
205
+ describe "#delete_all!" do
206
+ it "should return false if a store returns nil for write_all" do
207
+ undeletable, deletable = mock(:undeletable_store), mock(:deletable_store)
208
+ undeletable.should_receive(:delete_all!).and_return nil
209
+ deletable.should_receive(:delete_all!).and_return true
210
+
211
+ adhoc = Merb::Cache::AdhocStore.new
212
+ adhoc.stores = [undeletable, deletable]
213
+ adhoc.delete_all!.should be_false
214
+ end
215
+
216
+ it "should always call write_all on every store" do
217
+ undeletable, deletable, used = mock(:undeletable_store), mock(:deletable_store), mock(:used_store)
218
+ undeletable.stub!(:delete_all!).and_return nil
219
+ deletable.should_receive(:delete_all!).and_return true
220
+ used.should_receive(:delete_all!).and_return true
221
+
222
+ adhoc = Merb::Cache::AdhocStore.new
223
+ adhoc.stores = [undeletable, deletable, used]
224
+ adhoc.delete_all!.should be_false
225
+ end
226
+ end
227
+ end