shanboli-cache-money 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ module Cash
2
+ module Query
3
+ class Select < Abstract
4
+ delegate :find_every_without_cache, :to => :@active_record
5
+
6
+ protected
7
+ def miss(_, miss_options)
8
+ find_every_without_cache(miss_options)
9
+ end
10
+
11
+ def uncacheable
12
+ find_every_without_cache(@options1)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Cash
2
+ Request = {}
3
+ end
@@ -0,0 +1,42 @@
1
+ module Cash
2
+ class Transactional
3
+ attr_reader :memcache
4
+
5
+ def initialize(memcache, lock)
6
+ @memcache, @cache = [memcache, memcache]
7
+ @lock = lock
8
+ end
9
+
10
+ def transaction
11
+ exception_was_raised = false
12
+ begin_transaction
13
+ result = yield
14
+ rescue Object => e
15
+ exception_was_raised = true
16
+ raise
17
+ ensure
18
+ begin
19
+ @cache.flush unless exception_was_raised
20
+ ensure
21
+ end_transaction
22
+ end
23
+ end
24
+
25
+ def method_missing(method, *args, &block)
26
+ @cache.send(method, *args, &block)
27
+ end
28
+
29
+ def respond_to?(method)
30
+ @cache.respond_to?(method)
31
+ end
32
+
33
+ private
34
+ def begin_transaction
35
+ @cache = Buffered.push(@cache, @lock)
36
+ end
37
+
38
+ def end_transaction
39
+ @cache = @cache.pop
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ class Array
2
+ alias_method :count, :size
3
+
4
+ def to_hash
5
+ keys_and_values_without_nils = reject { |key, value| value.nil? }
6
+ shallow_flattened_keys_and_values_without_nils = keys_and_values_without_nils.inject([]) { |result, pair| result += pair }
7
+ Hash[*shallow_flattened_keys_and_values_without_nils]
8
+ end
9
+ end
@@ -0,0 +1,72 @@
1
+ module Cash
2
+ module WriteThrough
3
+ DEFAULT_TTL = 12.hours
4
+
5
+ def self.included(active_record_class)
6
+ active_record_class.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+ def self.included(active_record_class)
14
+ active_record_class.class_eval do
15
+ after_create :add_to_caches
16
+ after_update :update_caches
17
+ after_destroy :remove_from_caches
18
+ end
19
+ end
20
+
21
+ def add_to_caches
22
+ InstanceMethods.unfold(self.class, :add_to_caches, self)
23
+ end
24
+
25
+ def update_caches
26
+ InstanceMethods.unfold(self.class, :update_caches, self)
27
+ end
28
+
29
+ def remove_from_caches
30
+ return if new_record?
31
+ InstanceMethods.unfold(self.class, :remove_from_caches, self)
32
+ end
33
+
34
+ def expire_caches
35
+ InstanceMethods.unfold(self.class, :expire_caches, self)
36
+ end
37
+
38
+ def shallow_clone
39
+ clone = self.class.new
40
+ clone.instance_variable_set("@attributes", instance_variable_get(:@attributes))
41
+ clone.instance_variable_set("@new_record", new_record?)
42
+ clone
43
+ end
44
+
45
+ private
46
+ def self.unfold(klass, operation, object)
47
+ while klass < ActiveRecord::Base && klass.ancestors.include?(WriteThrough)
48
+ klass.send(operation, object)
49
+ klass = klass.superclass
50
+ end
51
+ end
52
+ end
53
+
54
+ module ClassMethods
55
+ def add_to_caches(object)
56
+ indices.each { |index| index.add(object) }
57
+ end
58
+
59
+ def update_caches(object)
60
+ indices.each { |index| index.update(object) }
61
+ end
62
+
63
+ def remove_from_caches(object)
64
+ indices.each { |index| index.remove(object) }
65
+ end
66
+
67
+ def expire_caches(object)
68
+ indices.each { |index| index.delete(object) }
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,159 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ module Cash
4
+ describe Accessor do
5
+ describe '#fetch' do
6
+ describe '#fetch("...")' do
7
+ describe 'when there is a cache miss' do
8
+ it 'returns nil' do
9
+ Story.fetch("yabba").should be_nil
10
+ end
11
+ end
12
+
13
+ describe 'when there is a cache hit' do
14
+ it 'returns the value of the cache' do
15
+ Story.set("yabba", "dabba")
16
+ Story.fetch("yabba").should == "dabba"
17
+ end
18
+ end
19
+ end
20
+
21
+ describe '#fetch([...])', :shared => true do
22
+ describe 'when there is a total cache miss' do
23
+ it 'yields the keys to the block' do
24
+ Story.fetch(["yabba", "dabba"]) { |*missing_ids| ["doo", "doo"] }.should == {
25
+ "Story:1/yabba" => "doo",
26
+ "Story:1/dabba" => "doo"
27
+ }
28
+ end
29
+ end
30
+
31
+ describe 'when there is a partial cache miss' do
32
+ it 'yields just the missing ids to the block' do
33
+ Story.set("yabba", "dabba")
34
+ Story.fetch(["yabba", "dabba"]) { |*missing_ids| "doo" }.should == {
35
+ "Story:1/yabba" => "dabba",
36
+ "Story:1/dabba" => "doo"
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#get' do
44
+ describe '#get("...")' do
45
+ describe 'when there is a cache miss' do
46
+ it 'returns the value of the block' do
47
+ Story.get("yabba") { "dabba" }.should == "dabba"
48
+ end
49
+
50
+ it 'adds to the cache' do
51
+ Story.get("yabba") { "dabba" }
52
+ Story.get("yabba").should == "dabba"
53
+ end
54
+ end
55
+
56
+ describe 'when there is a cache hit' do
57
+ before do
58
+ Story.set("yabba", "dabba")
59
+ end
60
+
61
+ it 'returns the value of the cache' do
62
+ Story.get("yabba") { "doo" }.should == "dabba"
63
+ end
64
+
65
+ it 'does nothing to the cache' do
66
+ Story.get("yabba") { "doo" }
67
+ Story.get("yabba").should == "dabba"
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#get([...])' do
73
+ it_should_behave_like "#fetch([...])"
74
+ end
75
+ end
76
+
77
+ describe '#incr' do
78
+ describe 'when there is a cache hit' do
79
+ before do
80
+ Story.set("count", 0)
81
+ end
82
+
83
+ it 'increments the value of the cache' do
84
+ Story.incr("count", 2)
85
+ Story.get("count", :raw => true).should =~ /2/
86
+ end
87
+
88
+ it 'returns the new cache value' do
89
+ Story.incr("count", 2).should == 2
90
+ end
91
+ end
92
+
93
+ describe 'when there is a cache miss' do
94
+ it 'initializes the value of the cache to the value of the block' do
95
+ Story.incr("count", 1) { 5 }
96
+ Story.get("count", :raw => true).should =~ /5/
97
+ end
98
+
99
+ it 'returns the new cache value' do
100
+ Story.incr("count", 1) { 2 }.should == 2
101
+ end
102
+ end
103
+ end
104
+
105
+ describe '#add' do
106
+ describe 'when the value already exists' do
107
+ it 'yields to the block' do
108
+ Story.set("count", 1)
109
+ Story.add("count", 1) { "yield me" }.should == "yield me"
110
+ end
111
+ end
112
+
113
+ describe 'when the value does not already exist' do
114
+ it 'adds the key to the cache' do
115
+ Story.add("count", 1)
116
+ Story.get("count").should == 1
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '#decr' do
122
+ describe 'when there is a cache hit' do
123
+ before do
124
+ Story.incr("count", 1) { 10 }
125
+ end
126
+
127
+ it 'decrements the value of the cache' do
128
+ Story.decr("count", 2)
129
+ Story.get("count", :raw => true).should =~ /8/
130
+ end
131
+
132
+ it 'returns the new cache value' do
133
+ Story.decr("count", 2).should == 8
134
+ end
135
+ end
136
+
137
+ describe 'when there is a cache miss' do
138
+ it 'initializes the value of the cache to the value of the block' do
139
+ Story.decr("count", 1) { 5 }
140
+ Story.get("count", :raw => true).should =~ /5/
141
+ end
142
+
143
+ it 'returns the new cache value' do
144
+ Story.decr("count", 1) { 2 }.should == 2
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '#cache_key' do
150
+ it 'uses the version number' do
151
+ Story.version 1
152
+ Story.cache_key("foo").should == "Story:1/foo"
153
+
154
+ Story.version 2
155
+ Story.cache_key("foo").should == "Story:2/foo"
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,199 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
+
3
+ module Cash
4
+ describe Finders do
5
+ describe 'when the cache is populated' do
6
+ describe "#find" do
7
+ describe '#find(id...)' do
8
+ describe '#find(id)' do
9
+ it "returns an active record" do
10
+ story = Story.create!(:title => 'a story')
11
+ Story.find(story.id).should == story
12
+ end
13
+ end
14
+
15
+ describe 'when the object is destroyed' do
16
+ describe '#find(id)' do
17
+ it "raises an error" do
18
+ story = Story.create!(:title => "I am delicious")
19
+ story.destroy
20
+ lambda { Story.find(story.id) }.should raise_error(ActiveRecord::RecordNotFound)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe '#find(id1, id2, ...)' do
26
+ it "returns an array" do
27
+ story1, story2 = Story.create!, Story.create!
28
+ Story.find(story1.id, story2.id).should == [story1, story2]
29
+ end
30
+
31
+ describe "#find(id, nil)" do
32
+ it "ignores the nils" do
33
+ story = Story.create!
34
+ Story.find(story.id, nil).should == story
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'when given nonexistent ids' do
40
+ describe 'when given one nonexistent id' do
41
+ it 'raises an error' do
42
+ lambda { Story.find(1) }.should raise_error(ActiveRecord::RecordNotFound)
43
+ end
44
+ end
45
+
46
+ describe 'when given multiple nonexistent ids' do
47
+ it "raises an error" do
48
+ lambda { Story.find(1, 2, 3) }.should raise_error(ActiveRecord::RecordNotFound)
49
+ end
50
+ end
51
+
52
+
53
+ describe '#find(nil)' do
54
+ it 'raises an error' do
55
+ lambda { Story.find(nil) }.should raise_error(ActiveRecord::RecordNotFound)
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ describe '#find(object)' do
62
+ it "coerces arguments to integers" do
63
+ story = Story.create!
64
+ Story.find(story.id.to_s).should == story
65
+ end
66
+ end
67
+
68
+ describe '#find([...])' do
69
+ describe 'when given an array with valid ids' do
70
+ it "#finds the object with that id" do
71
+ story = Story.create!
72
+ Story.find([story.id]).should == [story]
73
+ end
74
+ end
75
+
76
+ describe '#find([])' do
77
+ it 'returns the empty array' do
78
+ Story.find([]).should == []
79
+ end
80
+ end
81
+
82
+ describe 'when given nonexistent ids' do
83
+ it 'raises an error' do
84
+ lambda { Story.find([1, 2, 3]) }.should raise_error(ActiveRecord::RecordNotFound)
85
+ end
86
+ end
87
+
88
+ describe 'when given limits and offsets' do
89
+ describe '#find([1, 2, ...], :limit => ..., :offset => ...)' do
90
+ it "returns the correct slice of objects" do
91
+ character1 = Character.create!(:name => "Sam", :story_id => 1)
92
+ character2 = Character.create!(:name => "Sam", :story_id => 1)
93
+ character3 = Character.create!(:name => "Sam", :story_id => 1)
94
+ Character.find(
95
+ [character1.id, character2.id, character3.id],
96
+ :conditions => { :name => "Sam", :story_id => 1 }, :limit => 2
97
+ ).should == [character1, character2]
98
+ end
99
+ end
100
+
101
+ describe '#find([1], :limit => 0)' do
102
+ it "raises an error" do
103
+ character = Character.create!(:name => "Sam", :story_id => 1)
104
+ lambda do
105
+ Character.find([character.id], :conditions => { :name => "Sam", :story_id => 1 }, :limit => 0)
106
+ end.should raise_error(ActiveRecord::RecordNotFound)
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '#find(:first, ..., :offset => ...)' do
113
+ it "#finds the object in the correct order" do
114
+ story1 = Story.create!(:title => 'title1')
115
+ story2 = Story.create!(:title => story1.title)
116
+ Story.find(:first, :conditions => { :title => story1.title }, :offset => 1).should == story2
117
+ end
118
+ end
119
+
120
+ describe '#find(:first, :conditions => [])' do
121
+ it 'works' do
122
+ story = Story.create!
123
+ Story.find(:first, :conditions => []).should == story
124
+ end
125
+ end
126
+
127
+ describe "#find(:first, :conditions => '...')" do
128
+ it "uses the active record instance to typecast values extracted from the conditions" do
129
+ story1 = Story.create! :title => 'a story', :published => true
130
+ story2 = Story.create! :title => 'another story', :published => false
131
+ Story.get('published/false').should == [story2.id]
132
+ Story.find(:first, :conditions => 'published = 0').should == story2
133
+ end
134
+ end
135
+ end
136
+
137
+ describe '#find_by_attr' do
138
+ describe '#find_by_attr(nil)' do
139
+ it 'returns nil' do
140
+ Story.find_by_id(nil).should == nil
141
+ end
142
+ end
143
+
144
+ describe 'when given non-existent ids' do
145
+ it 'returns nil' do
146
+ Story.find_by_id(-1).should == nil
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '#find_all_by_attr' do
152
+ describe 'when given non-existent ids' do
153
+ it "does not raise an error" do
154
+ lambda { Story.find_all_by_id([-1, -2, -3]) }.should_not raise_error
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ describe 'when the cache is partially populated' do
161
+ describe '#find(:all, :conditions => ...)' do
162
+ it "returns the correct records" do
163
+ story1 = Story.create!(:title => title = 'once upon a time...')
164
+ $memcache.flush_all
165
+ story2 = Story.create!(:title => title)
166
+ Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
167
+ end
168
+ end
169
+
170
+ describe '#find(id1, id2, ...)' do
171
+ it "returns the correct records" do
172
+ story1 = Story.create!(:title => 'story 1')
173
+ $memcache.flush_all
174
+ story2 = Story.create!(:title => 'story 2')
175
+ Story.find(story1.id, story2.id).should == [story1, story2]
176
+ end
177
+ end
178
+ end
179
+
180
+ describe 'when the cache is not populated' do
181
+ describe '#find(id)' do
182
+ it "returns the correct records" do
183
+ story = Story.create!(:title => 'a story')
184
+ $memcache.flush_all
185
+ Story.find(story.id).should == story
186
+ end
187
+ end
188
+
189
+ describe '#find(id1, id2, ...)' do
190
+ it "handles finds with multiple ids correctly" do
191
+ story1 = Story.create!
192
+ story2 = Story.create!
193
+ $memcache.flush_all
194
+ Story.find(story1.id, story2.id).should == [story1, story2]
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end