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,68 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require File.dirname(__FILE__) + '/abstract_strategy_store_spec'
3
+
4
+ describe Merb::Cache::GzipStore do
5
+ it_should_behave_like 'all strategy stores'
6
+
7
+ before(:each) do
8
+ @klass = Merb::Cache::GzipStore
9
+ @store = Merb::Cache::GzipStore[DummyStore].new
10
+ end
11
+
12
+ describe "#writable?" do
13
+ it "should return true" do
14
+ @store.writable?(:foo).should be_true
15
+ @store.writable?('foo').should be_true
16
+ @store.writable?(123).should be_true
17
+ end
18
+
19
+ it "should be false if none of the context caches are writable" do
20
+ @store.stores.each {|s| s.should_receive(:writable?).and_return false}
21
+
22
+ @store.writable?(:foo).should be_false
23
+ end
24
+ end
25
+
26
+ describe "#read" do
27
+ it "should return nil if hash does not exist as a key in any context store" do
28
+ @store.stores.each {|s| s.should_receive(:read).with(:foo, :bar => :baz).and_return nil}
29
+
30
+ @store.read(:foo, :bar => :baz).should be_nil
31
+ end
32
+
33
+ it "should return the data from the context store when there is a hash key match" do
34
+ @store.stores.first.should_receive(:read).with(:foo, {}).and_return @store.compress("bar")
35
+
36
+ @store.read(:foo).should == "bar"
37
+ end
38
+ end
39
+
40
+ describe "#exists?" do
41
+ it "should return a boolean" do
42
+ @store.write('foo', 'bar')
43
+ @store.exists?('foo').should be_true
44
+ end
45
+ end
46
+
47
+ describe "#write" do
48
+ it "should write" do
49
+ @store.write(:foo, "bar").should be_true
50
+ end
51
+
52
+ it "should pass the hashed key to the context store" do
53
+ @store.stores.first.should_receive(:write).with(:foo, @store.compress('body'), {}, {}).and_return true
54
+
55
+ @store.write(:foo, 'body').should be_true
56
+ end
57
+
58
+ it "should use the parameters to create the hashed key" do
59
+ @store.stores.first.should_receive(:write).with(:foo, @store.compress('body'), {:bar => :baz}, {}).and_return true
60
+
61
+ @store.write(:foo, 'body', :bar => :baz).should be_true
62
+ end
63
+ end
64
+
65
+ describe "#fetch" do
66
+ # not sure how to spec this yet
67
+ end
68
+ end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require File.dirname(__FILE__) + '/abstract_strategy_store_spec'
3
+
4
+ describe Merb::Cache::MintCacheStore do
5
+ it_should_behave_like 'all strategy stores'
6
+
7
+ before(:each) do
8
+ @klass = Merb::Cache::MintCacheStore
9
+ @store = Merb::Cache::MintCacheStore[DummyStore].new
10
+ end
11
+
12
+ describe "reading caches" do
13
+ it "should return data after the first level cache has expired"
14
+ end
15
+
16
+ describe "knowing if a cache exists" do
17
+ it "should return a boolean" do
18
+ @store.write('foo', 'bar')
19
+ @store.exists?('foo').should be_true
20
+ end
21
+ end
22
+
23
+ describe "when writing a cache" do
24
+ it "should write" do
25
+ @store.write('foo', 'bar').should be_true
26
+ end
27
+
28
+ it "should write three keys" do
29
+ @store.write('foo', 'body')
30
+ %w(foo foo_validity foo_data).each do |key|
31
+ @store.read(key).should_not be_nil
32
+ end
33
+ end
34
+
35
+ it "should write a validity key that is a time object" do
36
+ @store.write('foo', 'body')
37
+ @store.read('foo_validity').first.should be_a_kind_of Time
38
+ end
39
+
40
+ it "should write a data key that is the same as a regular key" do
41
+ @store.write('foo', 'body')
42
+ @store.read('foo_data').first.should == @store.read('foo').first
43
+ end
44
+
45
+ it "should write the additional keys with double expiry time" do
46
+ @store.write('foo', 'body', {}, :expire_in => 10)
47
+ @store.read('foo_data')[2][:expire_in].should eql 20
48
+ end
49
+
50
+ it "should receive write_all three times (key, key_validity and key_data for write_all" do
51
+ @store.write_all('foo', 'body')
52
+ %w(foo foo_validity foo_data).each do |key|
53
+ @store.read(key).should_not be_nil
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ end
@@ -0,0 +1,146 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require File.dirname(__FILE__) + '/abstract_strategy_store_spec'
3
+
4
+ describe Merb::Cache::PageStore do
5
+ it_should_behave_like 'all strategy stores'
6
+
7
+ before(:each) do
8
+ Merb::Cache.stores.clear
9
+ Thread.current[:'merb-cache'] = nil
10
+
11
+ @klass = Merb::Cache::PageStore[:dummy]
12
+ Merb::Cache.register(:dummy, DummyStore)
13
+ Merb::Cache.register(:default, @klass)
14
+
15
+ @store = Merb::Cache[:default]
16
+ @dummy = @store.stores.first
17
+ end
18
+
19
+ def dispatch(url, attrs = {})
20
+ Merb::Dispatcher.handle(request_for(url, attrs))
21
+ end
22
+
23
+ def request_for(url, attrs = {})
24
+ Merb::Request.new(Rack::MockRequest.env_for(url, attrs))
25
+ end
26
+
27
+ describe "examples" do
28
+
29
+ class NhlScores < Merb::Controller
30
+ provides :xml, :json, :yaml
31
+
32
+ cache :index, :show
33
+ cache :overview
34
+
35
+ eager_cache :index, :overview, :uri => '/overview'
36
+ eager_cache :overview, :index
37
+ eager_cache(:overview, :show) {{ :uri => build_url(:show, :team => 1), :params => {:team => 1} }}
38
+
39
+ def index
40
+ "NHLScores index"
41
+ end
42
+
43
+ def show(team)
44
+ "NHLScores show(#{team})"
45
+ end
46
+
47
+ def overview
48
+ "NHLScores overview"
49
+ end
50
+ end
51
+
52
+ before(:each) do
53
+ Merb::Router.prepare do |r|
54
+ r.match("/").to(:controller => "nhl_scores", :action => "index").name(:index)
55
+ r.match("/show/:team").to(:controller => "nhl_scores", :action => "show").name(:show)
56
+ r.match("/overview").to(:controller => "nhl_scores", :action => "overview").name(:overview)
57
+ end
58
+ end
59
+
60
+
61
+ # NOTE: the "REQUEST_URI" => ... stuff will be removed after has_query_params? is in core.
62
+ it "should cache the index action on the first request" do
63
+ dispatch(url(:index), "REQUEST_URI" => url(:index))
64
+
65
+ @dummy.data("/index.html").should == "NHLScores index"
66
+ end
67
+
68
+ it "should cache the yaml version of the index action request" do
69
+ dispatch(url(:index), "HTTP_ACCEPT" => "application/x-yaml", "REQUEST_URI" => url(:index))
70
+
71
+ @dummy.data("/index.yaml").should == "NHLScores index"
72
+ end
73
+
74
+ it "should cache the show action when the team parameter is a route parameter" do
75
+ dispatch(url(:show, :team => 'redwings'), "REQUEST_URI" => url(:show, :team => 'redwings'))
76
+
77
+ @dummy.data("/show/redwings.html").should == "NHLScores show(redwings)"
78
+ end
79
+
80
+ it "should cache the xml version of a request" do
81
+ dispatch(url(:show, :team => 'redwings'), "HTTP_ACCEPT" => "application/xml", "REQUEST_URI" => url(:show, :team => 'redwings'))
82
+
83
+ @dummy.data("/show/redwings.xml").should == "NHLScores show(redwings)"
84
+ end
85
+
86
+
87
+ it "should not cache the show action when the team parameter is not a route parameter" do
88
+ pending "the has_query_params? is implemented in merb-core"
89
+ dispatch_to(NhlScores, :show, :team => 'readwings', "REQUEST_URI" => url(:show, :team => 'redwings'))
90
+
91
+ @dummy.vault.should be_empty
92
+ end
93
+
94
+ it "should not cache the action when a there is a query string parameter" do
95
+ dispatch(url(:index, :page => 2))
96
+
97
+ @dummy.data(url(:index)).should be_nil
98
+ end
99
+
100
+ it "should not cache a POST request" do
101
+ dispatch(url(:index), "REQUEST_METHOD" => "POST")
102
+
103
+ @dummy.data('/index.html').should be_nil
104
+ end
105
+
106
+ it "should eager cache overview after a POST request to index" do
107
+ dispatch(url(:index), "REQUEST_METHOD" => "POST")
108
+
109
+ @dummy.data('/overview.html').should == "NHLScores overview"
110
+ end
111
+
112
+ it "should eager cache show after a request to overview" do
113
+ dispatch(url(:overview))
114
+
115
+ @dummy.data('/show/1.html').should == "NHLScores show(1)"
116
+ end
117
+
118
+ it "should not eager cache during an eager cache, causing an infinit loop of eagerness" do
119
+ dispatch(url(:overview), "REQUEST_URI" => url(:overview))
120
+
121
+ @dummy.data("/overview.html").should == "NHLScores overview"
122
+ end
123
+
124
+ describe "controller.caches?" do
125
+ it "should return true for a request to overview with the :get method" do
126
+ NhlScores.caches?(:overview, :uri => url(:overview)).should be_true
127
+ end
128
+
129
+ it "should return false for a request to overview with the :post method" do
130
+ NhlScores.caches?(:overview, :uri => url(:overview), :method => :post).should be_false
131
+ end
132
+
133
+ it "should return false for a request to overview with query string parameters" do
134
+ NhlScores.caches?(:overview, :uri => url(:overview, :foo => 'baz')).should be_false
135
+ end
136
+
137
+ it "should return true for a request to overview with the :get method when looking in the page store" do
138
+ NhlScores.caches?(:overview, :uri => url(:overview), :store => :default).should be_true
139
+ end
140
+
141
+ it "should return false for a request to overview with the :get method when looking in the action store" do
142
+ pending "need to move all the specs for caches? over to a controller integration test that uses both controller stores"
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,84 @@
1
+ require File.dirname(__FILE__) + '/../../../spec_helper'
2
+ require File.dirname(__FILE__) + '/abstract_strategy_store_spec'
3
+
4
+ describe Merb::Cache::SHA1Store do
5
+ it_should_behave_like 'all strategy stores'
6
+
7
+ before(:each) do
8
+ @klass = Merb::Cache::SHA1Store
9
+ @store = Merb::Cache::SHA1Store[DummyStore].new
10
+ end
11
+
12
+ describe "#writable?" do
13
+ it "should return true" do
14
+ @store.writable?('foo').should be_true
15
+ end
16
+
17
+ it "should be false if none of the context caches are writable" do
18
+ @store.stores.each {|s| s.should_receive(:writable?).and_return false}
19
+
20
+ @store.writable?(:foo).should be_false
21
+ end
22
+ end
23
+
24
+ describe "#read" do
25
+ it "should return nil if hash does not exist as a key in any context store" do
26
+ @store.stores.each {|s| s.should_receive(:read).with(@store.digest(:foo, :bar => :baz)).and_return nil}
27
+
28
+ @store.read(:foo, :bar => :baz).should be_nil
29
+ end
30
+
31
+ it "should return the data from the context store when there is a hash key match" do
32
+ @store.stores.first.should_receive(:read).with(@store.digest(:foo)).and_return :bar
33
+
34
+ @store.read(:foo).should == :bar
35
+ end
36
+ end
37
+
38
+ describe "#exists?" do
39
+ it "should return a boolean" do
40
+ @store.write('foo', 'bar')
41
+ @store.exists?('foo').should be_true
42
+ end
43
+ end
44
+
45
+ describe "#write" do
46
+ it "should write" do
47
+ @store.write('foo', 'bar').should be_true
48
+ end
49
+
50
+ it "should pass the hashed key to the context store" do
51
+ @store.stores.first.should_receive(:write).with(@store.digest(:foo), 'body', {}, {}).and_return true
52
+
53
+ @store.write(:foo, 'body').should be_true
54
+ end
55
+
56
+ it "should use the parameters to create the hashed key" do
57
+ @store.stores.first.should_receive(:write).with(@store.digest(:foo, :bar => :baz), 'body', {}, {}).and_return true
58
+
59
+ @store.write(:foo, 'body', :bar => :baz).should be_true
60
+ end
61
+ end
62
+
63
+ describe "#fetch" do
64
+ it "should return nil if the arguments are not storable" do
65
+ @store.fetch(mock(:request)) {'body'}.should be_nil
66
+ end
67
+
68
+
69
+ end
70
+
71
+ describe "#digest" do
72
+ it "should produce the same digest for the exact same key and parameters" do
73
+ @store.digest(:foo, :bar => :baz).should == @store.digest(:foo, :bar => :baz)
74
+ end
75
+
76
+ it "should use the string result of the parameter arguments to_param in the hash" do
77
+ @store.digest(:foo, :bar => :baz).should_not == @store.digest(:foo)
78
+ end
79
+
80
+ it "should use the key argument in the hash" do
81
+ @store.digest('', :bar => :baz).should_not == @store.digest('')
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,95 @@
1
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ # Deps
4
+ require 'rubygems'
5
+ require 'merb-core'
6
+ require 'merb-action-args'
7
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'merb-cache')
8
+
9
+ Merb.disable(:initfile)
10
+
11
+ Merb.start :environment => "test",
12
+ :adapter => "runner",
13
+ :log_file => File.join(File.dirname(__FILE__), '..', 'log', 'merb_test.log')
14
+
15
+ require "merb-core/test"
16
+ Spec::Runner.configure do |config|
17
+ config.include Merb::Test::Helpers
18
+ #config.include Merb::Test::ControllerHelper
19
+ config.include Merb::Test::RouteHelper
20
+ end
21
+
22
+ # Usage
23
+ # http://github.com/hassox/merb/tree/authz/merb-core/spec/public/authorization/base_spec.rb
24
+ class Viking
25
+ def self.captures
26
+ @captures ||= []
27
+ end
28
+
29
+ def self.capture(obj)
30
+ captures << obj
31
+ end
32
+ end
33
+
34
+ class DummyStore < Merb::Cache::AbstractStore
35
+ cattr_accessor :vault
36
+ attr_accessor :options
37
+
38
+ def initialize(config = {})
39
+ super(config)
40
+ @options = config
41
+ @@vault = {}
42
+ end
43
+
44
+ def writable?(*args)
45
+ true
46
+ end
47
+
48
+ def read(key, parameters = {})
49
+
50
+ if @@vault.keys.include?(key)
51
+ @@vault[key].find {|data, timestamp, conditions, params| params == parameters}
52
+ end
53
+ end
54
+
55
+ def data(key, parameters = {})
56
+ read(key, parameters)[0] if read(key, parameters)
57
+ end
58
+
59
+ def time(key, parameters = {})
60
+ read(key, parameters)[1] if read(key, parameters)
61
+ end
62
+
63
+ def conditions(key, parameters = {})
64
+ read(key, parameters)[2] if read(key, parameters)
65
+ end
66
+
67
+ def write(key, data = nil, parameters = {}, conditions = {})
68
+ (@@vault[key] ||= []) << [data, Time.now, conditions, parameters]
69
+ true
70
+ end
71
+
72
+ def fetch(key, parameters = {}, conditions = {}, &blk)
73
+ @@vault[[key, parameters]] ||= blk.call
74
+ end
75
+
76
+ def exists?(key, parameters = {})
77
+ return true if @@vault.has_key?(key) and @@vault[key].find {|data, timestamp, conditions, params| params == parameters}
78
+ return false
79
+ end
80
+
81
+ def delete(key, parameters = {})
82
+ @@vault.delete([key, parameters]) unless @@vault[[key, parameters]].nil?
83
+ end
84
+
85
+ def delete_all
86
+ @@vault = {}
87
+ end
88
+ end
89
+
90
+ #TODO change this to a work queue per class called in an after aspect
91
+ class Merb::Controller
92
+ def run_later
93
+ yield
94
+ end
95
+ end