viximo-cache-money 0.3.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 (51) hide show
  1. data/LICENSE +201 -0
  2. data/README +204 -0
  3. data/README.markdown +204 -0
  4. data/TODO +17 -0
  5. data/UNSUPPORTED_FEATURES +13 -0
  6. data/config/environment.rb +8 -0
  7. data/config/memcached.yml +4 -0
  8. data/db/schema.rb +18 -0
  9. data/init.rb +1 -0
  10. data/lib/cache_money.rb +105 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/adapter/memcache_client.rb +36 -0
  13. data/lib/cash/adapter/memcached.rb +127 -0
  14. data/lib/cash/adapter/redis.rb +144 -0
  15. data/lib/cash/buffered.rb +137 -0
  16. data/lib/cash/config.rb +78 -0
  17. data/lib/cash/fake.rb +83 -0
  18. data/lib/cash/finders.rb +50 -0
  19. data/lib/cash/index.rb +211 -0
  20. data/lib/cash/local.rb +105 -0
  21. data/lib/cash/lock.rb +63 -0
  22. data/lib/cash/mock.rb +158 -0
  23. data/lib/cash/query/abstract.rb +219 -0
  24. data/lib/cash/query/calculation.rb +45 -0
  25. data/lib/cash/query/primary_key.rb +50 -0
  26. data/lib/cash/query/select.rb +16 -0
  27. data/lib/cash/request.rb +3 -0
  28. data/lib/cash/transactional.rb +43 -0
  29. data/lib/cash/util/array.rb +9 -0
  30. data/lib/cash/util/marshal.rb +19 -0
  31. data/lib/cash/version.rb +3 -0
  32. data/lib/cash/write_through.rb +71 -0
  33. data/lib/mem_cached_session_store.rb +49 -0
  34. data/lib/mem_cached_support_store.rb +143 -0
  35. data/rails/init.rb +1 -0
  36. data/spec/cash/accessor_spec.rb +186 -0
  37. data/spec/cash/active_record_spec.rb +224 -0
  38. data/spec/cash/buffered_spec.rb +9 -0
  39. data/spec/cash/calculations_spec.rb +78 -0
  40. data/spec/cash/finders_spec.rb +455 -0
  41. data/spec/cash/local_buffer_spec.rb +9 -0
  42. data/spec/cash/local_spec.rb +9 -0
  43. data/spec/cash/lock_spec.rb +110 -0
  44. data/spec/cash/marshal_spec.rb +60 -0
  45. data/spec/cash/order_spec.rb +172 -0
  46. data/spec/cash/transactional_spec.rb +602 -0
  47. data/spec/cash/window_spec.rb +195 -0
  48. data/spec/cash/without_caching_spec.rb +32 -0
  49. data/spec/cash/write_through_spec.rb +252 -0
  50. data/spec/spec_helper.rb +87 -0
  51. metadata +300 -0
@@ -0,0 +1 @@
1
+ require 'cache_money'
@@ -0,0 +1,186 @@
1
+ require "spec_helper"
2
+ require 'ruby-debug'
3
+
4
+ module Cash
5
+ describe Accessor do
6
+ describe '#fetch' do
7
+ describe '#fetch("...")' do
8
+ describe 'when there is a cache miss' do
9
+ it 'returns nil' do
10
+ Story.fetch("yabba").should be_nil
11
+ end
12
+ end
13
+
14
+ describe 'when there is a cache hit' do
15
+ it 'returns the value of the cache' do
16
+ Story.set("yabba", "dabba")
17
+ Story.fetch("yabba").should == "dabba"
18
+ end
19
+ end
20
+ end
21
+
22
+ describe '#fetch([...])', :shared => true do
23
+ describe '#fetch([])' do
24
+ it 'returns the empty hash' do
25
+ Story.fetch([]).should == {}
26
+ end
27
+ end
28
+
29
+ describe 'when there is a total cache miss' do
30
+ it 'yields the keys to the block' do
31
+ Story.fetch(["yabba", "dabba"]) { |*missing_ids| ["doo", "doo"] }.should == {
32
+ "Story:1/yabba" => "doo",
33
+ "Story:1/dabba" => "doo"
34
+ }
35
+ end
36
+ end
37
+
38
+ describe 'when there is a partial cache miss' do
39
+ it 'yields just the missing ids to the block' do
40
+ Story.set("yabba", "dabba")
41
+ Story.fetch(["yabba", "dabba"]) { |*missing_ids| "doo" }.should == {
42
+ "Story:1/yabba" => "dabba",
43
+ "Story:1/dabba" => "doo"
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#get' do
51
+ describe '#get("...")' do
52
+ describe 'when there is a cache miss' do
53
+ it 'returns the value of the block' do
54
+ Story.get("yabba") { "dabba" }.should == "dabba"
55
+ end
56
+
57
+ it 'adds to the cache' do
58
+ Story.get("yabba") { "dabba" }
59
+ Story.get("yabba").should == "dabba"
60
+ end
61
+ end
62
+
63
+ describe 'when there is a cache hit' do
64
+ before do
65
+ Story.set("yabba", "dabba")
66
+ end
67
+
68
+ it 'returns the value of the cache' do
69
+ Story.get("yabba") { "doo" }.should == "dabba"
70
+ end
71
+
72
+ it 'does nothing to the cache' do
73
+ Story.get("yabba") { "doo" }
74
+ Story.get("yabba").should == "dabba"
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '#get([...])' do
80
+ it_should_behave_like "#fetch([...])"
81
+ end
82
+ end
83
+
84
+ describe '#add' do
85
+ describe 'when the value already exists' do
86
+ describe 'when a block is given' do
87
+ it 'yields to the block' do
88
+ Story.set("count", 1)
89
+ Story.add("count", 1) { "yield me" }.should == "yield me"
90
+ end
91
+ end
92
+
93
+ describe 'when no block is given' do
94
+ it 'does not error' do
95
+ Story.set("count", 1)
96
+ lambda { Story.add("count", 1) }.should_not raise_error
97
+ end
98
+ end
99
+ end
100
+
101
+ describe 'when the value does not already exist' do
102
+ it 'adds the key to the cache' do
103
+ Story.add("count", 1)
104
+ Story.get("count").should == 1
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#set' do
110
+ end
111
+
112
+ describe '#incr' do
113
+ describe 'when there is a cache hit' do
114
+ before do
115
+ Story.set("count", 0, :raw => true)
116
+ end
117
+
118
+ it 'increments the value of the cache' do
119
+ Story.incr("count", 2)
120
+ Story.get("count", :raw => true).should =~ /2/
121
+ end
122
+
123
+ it 'returns the new cache value' do
124
+ Story.incr("count", 2).should == 2
125
+ end
126
+ end
127
+
128
+ describe 'when there is a cache miss' do
129
+ it 'initializes the value of the cache to the value of the block' do
130
+ Story.incr("count", 1) { 5 }
131
+ Story.get("count", :raw => true).should =~ /5/
132
+ end
133
+
134
+ it 'returns the new cache value' do
135
+ Story.incr("count", 1) { 2 }.should == 2
136
+ end
137
+ end
138
+ end
139
+
140
+ describe '#decr' do
141
+ describe 'when there is a cache hit' do
142
+ before do
143
+ Story.incr("count", 1) { 10 }
144
+ end
145
+
146
+ it 'decrements the value of the cache' do
147
+ Story.decr("count", 2)
148
+ Story.get("count", :raw => true).should =~ /8/
149
+ end
150
+
151
+ it 'returns the new cache value' do
152
+ Story.decr("count", 2).should == 8
153
+ end
154
+ end
155
+
156
+ describe 'when there is a cache miss' do
157
+ it 'initializes the value of the cache to the value of the block' do
158
+ Story.decr("count", 1) { 5 }
159
+ Story.get("count", :raw => true).should =~ /5/
160
+ end
161
+
162
+ it 'returns the new cache value' do
163
+ Story.decr("count", 1) { 2 }.should == 2
164
+ end
165
+ end
166
+ end
167
+
168
+ describe '#expire' do
169
+ it 'deletes the key' do
170
+ Story.set("bobo", 1)
171
+ Story.expire("bobo")
172
+ Story.get("bobo").should == nil
173
+ end
174
+ end
175
+
176
+ describe '#cache_key' do
177
+ it 'uses the version number' do
178
+ Story.version 1
179
+ Story.cache_key("foo").should == "Story:1/foo"
180
+
181
+ Story.version 2
182
+ Story.cache_key("foo").should == "Story:2/foo"
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,224 @@
1
+ require "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, ...)' do
113
+ describe '#find(:first, ..., :offset => ...)' do
114
+ it "#finds the object in the correct order" do
115
+ story1 = Story.create!(:title => 'title1')
116
+ story2 = Story.create!(:title => story1.title)
117
+ Story.find(:first, :conditions => { :title => story1.title }, :offset => 1).should == story2
118
+ end
119
+ end
120
+
121
+ describe '#find(:first, :conditions => [])' do
122
+ it 'finds the object in the correct order' do
123
+ story = Story.create!
124
+ Story.find(:first, :conditions => []).should == story
125
+ end
126
+ end
127
+
128
+ describe "#find(:first, :conditions => '...')" do
129
+ it "coerces ruby values to the appropriate database values" do
130
+ story1 = Story.create! :title => 'a story', :published => true
131
+ story2 = Story.create! :title => 'another story', :published => false
132
+ Story.find(:first, :conditions => 'published = 0').should == story2
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ describe '#find_by_attr' do
139
+ describe '#find_by_attr(nil)' do
140
+ it 'returns nil' do
141
+ Story.find_by_id(nil).should == nil
142
+ end
143
+ end
144
+
145
+ describe 'when given non-existent ids' do
146
+ it 'returns nil' do
147
+ Story.find_by_id(-1).should == nil
148
+ end
149
+ end
150
+ end
151
+
152
+ describe '#find_all_by_attr' do
153
+ describe 'when given non-existent ids' do
154
+ it "does not raise an error" do
155
+ lambda { Story.find_all_by_id([-1, -2, -3]) }.should_not raise_error
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ describe 'when the cache is partially populated' do
162
+ describe '#find(:all, :conditions => ...)' do
163
+ it "returns the correct records" do
164
+ story1 = Story.create!(:title => title = 'once upon a time...')
165
+ $memcache.flush_all
166
+ story2 = Story.create!(:title => title)
167
+ Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
168
+ end
169
+ end
170
+
171
+ describe '#find(id1, id2, ...)' do
172
+ it "returns the correct records" do
173
+ story1 = Story.create!(:title => 'story 1')
174
+ $memcache.flush_all
175
+ story2 = Story.create!(:title => 'story 2')
176
+ Story.find(story1.id, story2.id).should == [story1, story2]
177
+ end
178
+ end
179
+ end
180
+
181
+ describe 'when the cache is not populated' do
182
+ describe '#find(id)' do
183
+ it "returns the correct records" do
184
+ story = Story.create!(:title => 'a story')
185
+ $memcache.flush_all
186
+ Story.find(story.id).should == story
187
+ end
188
+
189
+ it "handles after_find on model" do
190
+ class AfterFindStory < Story
191
+ def after_find
192
+ self.title
193
+ end
194
+ end
195
+ lambda do
196
+ AfterFindStory.create!(:title => 'a story')
197
+ end.should_not raise_error(ActiveRecord::MissingAttributeError)
198
+ end
199
+ end
200
+
201
+ describe '#find(id1, id2, ...)' do
202
+ it "handles finds with multiple ids correctly" do
203
+ story1 = Story.create!
204
+ story2 = Story.create!
205
+ $memcache.flush_all
206
+ Story.find(story1.id, story2.id).should == [story1, story2]
207
+ end
208
+ end
209
+ end
210
+
211
+ describe 'loading' do
212
+ it "should be able to create a record for an ar subclass that was loaded before cache money" do
213
+ $debug = true
214
+ session = ActiveRecord::SessionStore::Session.new
215
+ session.session_id = "12345"
216
+ session.data = "foobarbaz"
217
+
218
+ lambda {
219
+ session.save!
220
+ }.should_not raise_error
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe Buffered do
5
+ it "should have method missing as a private method" do
6
+ Buffered.private_instance_methods.should include("method_missing")
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe Finders do
5
+ describe 'Calculations' do
6
+ describe 'when the cache is populated' do
7
+ before do
8
+ @stories = [Story.create!(:title => @title = 'asdf'), Story.create!(:title => @title)]
9
+ end
10
+
11
+ describe '#count(:all, :conditions => ...)' do
12
+ it "does not use the database" do
13
+ Story.count(:all, :conditions => { :title => @title }).should == @stories.size
14
+ end
15
+ end
16
+
17
+ describe '#count(:column, :conditions => ...)' do
18
+ it "uses the database, not the cache" do
19
+ mock(Story).get.never
20
+ Story.count(:title, :conditions => { :title => @title }).should == @stories.size
21
+ end
22
+ end
23
+
24
+ describe '#count(:all, :distinct => ..., :select => ...)' do
25
+ it 'uses the database, not the cache' do
26
+ mock(Story).get.never
27
+ Story.count(:all, :distinct => true, :select => :title, :conditions => { :title => @title }).should == @stories.collect(&:title).uniq.size
28
+ end
29
+ end
30
+
31
+ describe 'association proxies' do
32
+ describe '#count(:all, :conditions => ...)' do
33
+ it 'does not use the database' do
34
+ story = Story.create!
35
+ characters = [story.characters.create!(:name => name = 'name'), story.characters.create!(:name => name)]
36
+ mock(Story.connection).execute.never
37
+ story.characters.count(:all, :conditions => { :name => name }).should == characters.size
38
+ end
39
+
40
+ it 'has correct counter cache' do
41
+ story = Story.create!
42
+ characters = [story.characters.create!(:name => name = 'name'), story.characters.create!(:name => name)]
43
+ $memcache.flush_all
44
+ story.characters.find(:all, :conditions => { :name => name }) == characters
45
+ story.characters.count(:all, :conditions => { :name => name }).should == characters.size
46
+ story.characters.find(:all, :conditions => { :name => name }) == characters
47
+ mock(Story.connection).execute.never
48
+ story.characters.count(:all, :conditions => { :name => name }).should == characters.size
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ describe 'when the cache is not populated' do
55
+ describe '#count(:all, ...)' do
56
+ describe '#count(:all)' do
57
+ it 'uses the database, not the cache' do
58
+ mock(Story).get.never
59
+ Story.count
60
+ end
61
+ end
62
+
63
+ describe '#count(:all, :conditions => ...)' do
64
+ before do
65
+ Story.create!(:title => @title = 'title')
66
+ $memcache.flush_all
67
+ end
68
+
69
+ it "populates the count correctly" do
70
+ Story.count(:all, :conditions => { :title => @title }).should == 1
71
+ Story.fetch("title/#{@title}/count", :raw => true).should =~ /\s*1\s*/
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end