stacks 0.1.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.
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stacks::ColumnDependentCache do
4
+
5
+ describe ".validate_model" do
6
+
7
+ it "throws an exception if the model isn't an activerecord model" do
8
+ expect do
9
+ Stacks::ColumnDependentCache.validate_model(FakeModel)
10
+ end.to raise_error(Stacks::ColumnDependentCache::InvalidModel)
11
+ end
12
+
13
+ it "doesn't throw an exception if the class is valid" do
14
+ expect do
15
+ Stacks::ColumnDependentCache.validate_model(TestModel)
16
+ end.to_not raise_error
17
+ end
18
+
19
+ end
20
+
21
+ describe ".get_backend" do
22
+
23
+ it "returns a backend namespaced to the model" do
24
+ backend = Stacks::ColumnDependentCache.get_backend(TestModel)
25
+ backend.namespace.should == TestModel.to_s
26
+ end
27
+
28
+ end
29
+
30
+ describe ".cached" do
31
+
32
+ before(:each) do
33
+ @model = TestModel
34
+ @columns = [:col1, :col2, :col3]
35
+ @identifier = "identifier"
36
+ end
37
+
38
+ let(:call) do
39
+ Stacks::ColumnDependentCache.cached(@model,
40
+ @columns,
41
+ @identifier) { "wussup?" }
42
+ end
43
+
44
+ it "validates the model" do
45
+ Stacks::ColumnDependentCache.should_receive(:validate_model).with(@model)
46
+ call
47
+ end
48
+
49
+ context "test actual cacheing" do
50
+
51
+ before(:each) do
52
+ @model = TestClass
53
+ @model.stub_chain(:nbc_listener, :register_column)
54
+ Stacks::ColumnDependentCache.stub(:validate_model)
55
+ @backend = double
56
+ end
57
+
58
+
59
+ it "uses a backend appropriate to the model" do
60
+ @backend.stub(:get_or_set)
61
+
62
+ Stacks::ColumnDependentCache.should_receive(:get_backend).with(@model).and_return(@backend)
63
+
64
+ call
65
+ end
66
+
67
+ it "calls get_or_set on the backend" do
68
+ item = double
69
+ Stacks::Items::ColumnDependentBlock.stub(:new) { item }
70
+ Stacks::ColumnDependentCache.stub(:get_backend) { @backend }
71
+
72
+ @backend.should_receive(:get_or_set).with(item,
73
+ Stacks::ColumnDependentCache.default_ttl)
74
+
75
+ call
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+
82
+ describe ".bust_cache" do
83
+
84
+ it "runs" do
85
+ backend = Stacks::Backends::NamespacedBackend.new
86
+ backend.namespace = "namespace"
87
+ Stacks::ColumnDependentCache.stub(:get_backend) { backend }
88
+ Stacks::ColumnDependentCache.bust_cache(TestModel, [:key1])
89
+ end
90
+
91
+ context "stubbed backend" do
92
+
93
+ before(:each) do
94
+ @backend = double
95
+ @item = double
96
+ @item.stub(:clear_value)
97
+ @backend.stub(:keys) { ["identifier:key1", "identifier:key2"] }
98
+ Stacks::ColumnDependentCache.stub(:get_backend) { @backend }
99
+ end
100
+
101
+ it "doesn't delete a key when it's a fillable block key" do
102
+ Stacks::ColumnDependentCache.stub(:fillable_block_keys).and_return do
103
+ { TestModel => { "identifier:key2" => @item } }
104
+ end
105
+
106
+ Stacks::ColumnDependentCache.should_receive(:make_fill_decision)
107
+ .with(TestModel, "identifier:key2")
108
+ @backend.should_receive(:del_key).exactly(0).times
109
+ Stacks::ColumnDependentCache.bust_cache(TestModel, [:key2])
110
+ end
111
+
112
+ it "should delete each the key with given attribute" do
113
+ @backend.should_receive(:del_key).exactly(1).times.with("identifier:key1")
114
+ Stacks::ColumnDependentCache.bust_cache(TestModel, [:key1])
115
+ end
116
+
117
+ it "should delete a key with multiple columns, with one that matches" do
118
+ @backend.stub(:keys) { ["identifier:key3:key7:key1", "identifier:key2"] }
119
+ @backend.should_receive(:del_key).exactly(1).times.with("identifier:key3:key7:key1")
120
+ Stacks::ColumnDependentCache.bust_cache(TestModel, [:key1])
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+
127
+ describe ".fill_block" do
128
+
129
+ it "should set the value when called with the correct key" do
130
+ Stacks::ColumnDependentCache.define_block(TestModel, [:test_column1], "test_identifier") do
131
+ "test_value"
132
+ end
133
+
134
+ Stacks::ColumnDependentCache.fillable_block_keys.keys.first.should == TestModel
135
+ Stacks::ColumnDependentCache.defined_blocks.keys.should == ["test_identifier"]
136
+
137
+ Stacks::ColumnDependentCache.fill_block(TestModel,
138
+ "test_identifier:test_column1")
139
+
140
+ Stacks::Backends::NamespacedBackend.any_instance.should_not_receive(:set)
141
+
142
+ Stacks::ColumnDependentCache.get_block_value("test_identifier").should ==
143
+ "test_value"
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe Stacks::Items::ColumnDependentBlock do
4
+
5
+ before(:each) do
6
+ @model = TestModel
7
+ @columns = [:column1]
8
+ @identifier = "identifier"
9
+ @proc = lambda { "woo" }
10
+
11
+ @cdb = Stacks::Items::ColumnDependentBlock.new(@model,
12
+ @columns,
13
+ @identifier,
14
+ @proc)
15
+ end
16
+
17
+ describe "#key" do
18
+
19
+ it "consists of the identifier and the columns" do
20
+ @cdb.key.should == "identifier:column1"
21
+ end
22
+
23
+ end
24
+
25
+ describe ".key_to_columns" do
26
+
27
+ it "drops the identifier from the key and returns the columns"do
28
+ Stacks::Items::ColumnDependentBlock.key_to_columns("identifier:column1").should ==
29
+ ["column1"]
30
+ end
31
+
32
+ end
33
+
34
+ describe "#value" do
35
+
36
+ it "just calls the proc" do
37
+ @cdb.value.should == "woo"
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+
3
+ describe Stacks::Items::MethodCall do
4
+
5
+ before(:each) do
6
+ @instance = TestClass.new
7
+ @args = ["woo", "yoo"]
8
+ @method_call = Stacks::Items::MethodCall.new(@instance, :test_method, @args)
9
+ end
10
+
11
+ describe "#key_str" do
12
+
13
+ it "contains the method name" do
14
+ @method_call.key_str.include?(:test_method.to_s).should be_true
15
+ end
16
+
17
+ it "contains the marshaled object" do
18
+ @method_call.key_str.include?(Marshal.dump(@instance)).should be_true
19
+ end
20
+
21
+ it "contains the marshaled arguments" do
22
+ @method_call.key_str.include?(Marshal.dump(@args)).should be_true
23
+ end
24
+
25
+ end
26
+
27
+ describe "#key" do
28
+
29
+ it "contains the SHA2 value of the key_str" do
30
+ @method_call.key.should == Digest::SHA2.hexdigest(@method_call.key_str)
31
+ end
32
+
33
+ end
34
+
35
+ describe "#value" do
36
+
37
+ it "calls the method with the arguments" do
38
+ @method_call.value.should == "My arguments: woo and yoo"
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe Stacks::Items::Proc do
4
+
5
+ describe "#initialize" do
6
+
7
+ it "keeps track of a proc" do
8
+ test_proc = proc { "hello" }
9
+ item = Stacks::Items::Proc.new("test-identifier", test_proc)
10
+
11
+ item.key.should == "test-identifier"
12
+ item.value.should == "hello"
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Stacks::MethodCache do
4
+
5
+ before(:each) do
6
+ @instance = TestClass.new
7
+ end
8
+
9
+ describe ".cached" do
10
+
11
+ before(:each) do
12
+ Stacks::MethodCache.cached(@instance,
13
+ :test_method2,
14
+ ["woo", "yoo"],
15
+ 666).should == "My arguments: woo and yoo"
16
+ end
17
+
18
+ it "should return the method value" do
19
+ Stacks::MethodCache.cached(@instance,
20
+ :test_method2,
21
+ ["woo", "yoo"],
22
+ 666).should == "My arguments: woo and yoo"
23
+ end
24
+
25
+ it "should return the same value even if the method result" do
26
+ TestClass.prefix = "HAHA"
27
+ @instance.test_method2("woo", "yoo").should == "HAHA: woo and yoo"
28
+
29
+ Stacks::MethodCache.cached(@instance,
30
+ :test_method2,
31
+ ["woo", "yoo"],
32
+ 666).should == "My arguments: woo and yoo"
33
+ TestClass.prefix = "My arguments"
34
+ end
35
+
36
+ it "shouldn't conflict with other cached methods" do
37
+ Stacks::MethodCache.cached(@instance,
38
+ :test_method3,
39
+ [],
40
+ 666).should == "this is a test"
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,106 @@
1
+ require "spec_helper"
2
+
3
+ describe Stacks::ModelExtensions do
4
+
5
+ describe ".bust_cache_for_column" do
6
+
7
+ it "documents that a cache is watching a column" do
8
+ Stacks.model_listening_caches.each do |cache|
9
+ cache.should_receive(:bust_cache).with(TestModel, [:test_column])
10
+ end
11
+
12
+ Stacks::ModelExtensions.bust_cache_for_column(TestModel, :test_column)
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ describe Stacks::ModelExtensions::Extension do
20
+
21
+ describe ".stacks_watched_columns" do
22
+
23
+ it "is attached to every model" do
24
+ ActiveRecord::Base.stacks_watched_columns.is_a?(Set).should be_true
25
+ end
26
+
27
+ end
28
+
29
+ describe ".stacks_watch_columns" do
30
+
31
+ after(:each) { TestModel.stacks_watched_columns.clear }
32
+
33
+ it "adds a column to the watch set" do
34
+ TestModel.stacks_watched_columns.should == Set.new
35
+
36
+ TestModel.stacks_watch_column(:test_column1)
37
+ TestModel.stacks_watch_column(:test_column2)
38
+
39
+ TestModel.stacks_watched_columns.should == Set.new([:test_column1, :test_column2])
40
+ end
41
+
42
+ end
43
+
44
+ describe ".bust_stackss" do
45
+
46
+ after(:each) { TestModel.stacks_watched_columns.clear }
47
+
48
+ it "busts caches for each watched attribute" do
49
+ TestModel.stacks_watch_column(:test_column1)
50
+ TestModel.stacks_watch_column(:test_column2)
51
+
52
+ TestModel.stacks_watched_columns.count.should == 2
53
+
54
+ Stacks::ModelExtensions.should_receive(:bust_cache_for_columns).with(TestModel,
55
+ Set.new([:test_column1,
56
+ :test_column2]))
57
+
58
+ TestModel.bust_stacks
59
+ end
60
+
61
+ end
62
+
63
+ describe ".stacks_check_columns" do
64
+
65
+ it "is included in all models now" do
66
+ ActiveRecord::Base.instance_methods.include?(:stacks_check_columns).should be_true
67
+ end
68
+
69
+ context "modify column" do
70
+
71
+ before(:each) do
72
+ TestModel.stacks_watch_column(:test_column1)
73
+ end
74
+
75
+ after(:each) { TestModel.stacks_watched_columns.clear }
76
+
77
+ it "busts the column cache when the attribute is modified" do
78
+ Stacks.model_listening_caches.each do |cache|
79
+ cache.should_receive(:bust_cache).exactly(1).times.with(TestModel, [:test_column1])
80
+ end
81
+
82
+ y = TestModel.create
83
+ y.test_column1 = "hello!"
84
+ y.save!
85
+ end
86
+
87
+ it "busts the column cache when the attribute is modified at creation time" do
88
+ Stacks.model_listening_caches.each do |cache|
89
+ cache.should_receive(:bust_cache).exactly(1).times.with(TestModel, [:test_column1])
90
+ end
91
+
92
+ y = TestModel.create(:test_column1 => "hello!")
93
+ end
94
+
95
+ it "doesn't trigger the callback for a non-watched column" do
96
+ Stacks.model_listening_caches.each do |cache|
97
+ cache.should_receive(:bust_cache).exactly(0).times
98
+ end
99
+
100
+ y = TestModel.create(:test_column2 => "hello!")
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,141 @@
1
+ require "spec_helper"
2
+
3
+ describe Stacks::ReportCache do
4
+
5
+ before(:each) do
6
+ @report = Stacks::ReportCache.new("test report", 100)
7
+ end
8
+
9
+ describe "#backend" do
10
+
11
+ it "uses a namespaced backend customized to the report name" do
12
+ backend = @report.backend
13
+ backend.namespace.should == "test report"
14
+ end
15
+
16
+ end
17
+
18
+ describe ".report" do
19
+
20
+ it "allows defining values through passing a block" do
21
+ report = Stacks::ReportCache.report("test report", 100) do
22
+ value("a test value") { "woooo" }
23
+ end
24
+
25
+ report.report_name.should == "test report"
26
+ report.ttl.should == 100
27
+ report.values["a test value"].key.should == "a test value"
28
+ report.values["a test value"].value.should == "woooo"
29
+ end
30
+
31
+ end
32
+
33
+ describe "#value" do
34
+
35
+ it "defines a value for the report" do
36
+ @report.value("hello there") { "wussup" }
37
+ @report.values["hello there"].value.should == "wussup"
38
+ end
39
+
40
+ end
41
+
42
+ describe "#get_value" do
43
+
44
+ it "returns the cached value stored for the key" do
45
+ @report.value("hello there") { "wussup" }
46
+ @report.get_value("hello there").should == "wussup"
47
+ end
48
+
49
+ it "doesn't return a cached value if cache_condition evaluates to false" do
50
+ @report.value("hello there") { "wussup" }
51
+ @report.set_cache_condition { false }
52
+ item = @report.values["hello there"]
53
+ item.should_receive(:value)
54
+ @report.backend.should_receive(:get_or_set).exactly(0).times
55
+
56
+ @report.get_value("hello there")
57
+ end
58
+
59
+ it "returns a cached value if cache_condition evaluates to true" do
60
+ @report.value("hello there") { "wussup" }
61
+ @report.set_cache_condition { true }
62
+ item = @report.values["hello there"]
63
+ @report.backend.should_receive(:get_or_set)
64
+
65
+ @report.get_value("hello there")
66
+ end
67
+
68
+ end
69
+
70
+ describe "#fill_cache" do
71
+
72
+ it "should fill the cache" do
73
+ @report.value("hello there") { "wussup" }
74
+ @report.value("yo") { "ratchet" }
75
+
76
+ @report.values["hello there"].class.should == Stacks::Items::Proc
77
+ @report.values["yo"].class.should == Stacks::Items::Proc
78
+
79
+ @report.backend.should_receive(:fill).with(@report.values["hello there"], 100)
80
+ @report.backend.should_receive(:fill).with(@report.values["yo"], 100)
81
+
82
+ timestamp = double
83
+
84
+ Stacks::Items::Timestamp.stub(:new).and_return(timestamp)
85
+ @report.backend.should_receive(:fill).with(timestamp, 100)
86
+
87
+ @report.fill_cache
88
+ end
89
+
90
+ it "shouldn't do anything if the cache condition isn't satisfied" do
91
+ @report.value("yo") { "ratchet" }
92
+ @report.values["yo"].class.should == Stacks::Items::Proc
93
+ @report.set_cache_condition { false }
94
+ @report.backend.should_receive(:fill).exactly(0).times
95
+
96
+ @report.fill_cache
97
+ end
98
+
99
+ end
100
+
101
+ describe ".reports" do
102
+
103
+ it "keeps track of all defined reports" do
104
+ report = Stacks::ReportCache.report("test report", 100) do
105
+ value("a test value") { "woooo" }
106
+ end
107
+
108
+ Stacks::ReportCache.reports["test report"].should == report
109
+ Stacks::ReportCache.get_report("test report").should == report
110
+ end
111
+
112
+ end
113
+
114
+ describe "#register_instance_variables" do
115
+
116
+ it "transfers create attributes for each value" do
117
+ instance = TestClass.new
118
+ report = Stacks::ReportCache.report("test report", 100) do
119
+ value("my_attr") { "woooo" }
120
+ end
121
+
122
+ report.register_instance_variables(instance)
123
+ instance.instance_eval { @my_attr }.should == "woooo"
124
+ end
125
+
126
+ end
127
+
128
+ describe "#timestamp" do
129
+
130
+ it "it returns a timestamp" do
131
+ report = Stacks::ReportCache.report("test report", 100) do
132
+ value("my_attr") { "woooo" }
133
+ end
134
+
135
+ report.timestamp.class.should == Time
136
+ end
137
+
138
+ end
139
+
140
+
141
+ end