benschwarz-merb-cache 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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