sreeix-cache-money 0.2.24.1

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 (49) hide show
  1. data/LICENSE +201 -0
  2. data/README +210 -0
  3. data/README.markdown +210 -0
  4. data/TODO +17 -0
  5. data/UNSUPPORTED_FEATURES +13 -0
  6. data/config/environment.rb +16 -0
  7. data/config/memcached.yml +6 -0
  8. data/db/schema.rb +18 -0
  9. data/init.rb +1 -0
  10. data/lib/cache_money.rb +91 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/buffered.rb +129 -0
  13. data/lib/cash/config.rb +75 -0
  14. data/lib/cash/fake.rb +83 -0
  15. data/lib/cash/finders.rb +38 -0
  16. data/lib/cash/index.rb +214 -0
  17. data/lib/cash/local.rb +76 -0
  18. data/lib/cash/lock.rb +63 -0
  19. data/lib/cash/mock.rb +154 -0
  20. data/lib/cash/query/abstract.rb +210 -0
  21. data/lib/cash/query/calculation.rb +45 -0
  22. data/lib/cash/query/primary_key.rb +50 -0
  23. data/lib/cash/query/select.rb +16 -0
  24. data/lib/cash/request.rb +3 -0
  25. data/lib/cash/transactional.rb +43 -0
  26. data/lib/cash/util/array.rb +9 -0
  27. data/lib/cash/util/marshal.rb +19 -0
  28. data/lib/cash/write_through.rb +69 -0
  29. data/lib/mem_cached_session_store.rb +49 -0
  30. data/lib/mem_cached_support_store.rb +135 -0
  31. data/lib/memcached_wrapper.rb +263 -0
  32. data/rails/init.rb +38 -0
  33. data/spec/cash/accessor_spec.rb +159 -0
  34. data/spec/cash/active_record_spec.rb +224 -0
  35. data/spec/cash/buffered_spec.rb +9 -0
  36. data/spec/cash/calculations_spec.rb +78 -0
  37. data/spec/cash/finders_spec.rb +430 -0
  38. data/spec/cash/local_buffer_spec.rb +9 -0
  39. data/spec/cash/local_spec.rb +9 -0
  40. data/spec/cash/lock_spec.rb +108 -0
  41. data/spec/cash/marshal_spec.rb +60 -0
  42. data/spec/cash/order_spec.rb +138 -0
  43. data/spec/cash/shared.rb +62 -0
  44. data/spec/cash/transactional_spec.rb +578 -0
  45. data/spec/cash/window_spec.rb +195 -0
  46. data/spec/cash/write_through_spec.rb +245 -0
  47. data/spec/memcached_wrapper_test.rb +209 -0
  48. data/spec/spec_helper.rb +60 -0
  49. metadata +151 -0
@@ -0,0 +1,263 @@
1
+ # require 'memcache'
2
+ # require 'memcached'
3
+
4
+ #typically MemCache/Memcached can be used via the following in rails/init.rb:
5
+ #$memcache = MemCache.new(memcache_config[:servers].gsub(' ', '').split(','), memcache_config)
6
+ #$memcache = Memcached::Rails.new(memcache_config[:servers].gsub(' ', '').split(','), memcache_config)
7
+
8
+ #this wrapper lets both work.
9
+
10
+ ########## they have Memcached installed (do need the wrapper)
11
+ if defined? Memcached
12
+ Rails.logger.info("cache-money: Memcached installed") if defined? Rails
13
+
14
+ class Memcached
15
+ alias :get_multi :get #:nodoc:
16
+ end
17
+
18
+ class MemcachedWrapper < ::Memcached
19
+ DEFAULTS = { :servers => '127.0.0.1:11211' }
20
+
21
+ attr_reader :logger, :default_ttl
22
+
23
+ # See Memcached#new for details.
24
+ def initialize(*args)
25
+ opts = DEFAULTS.merge(args.last.is_a?(Hash) ? args.pop : {})
26
+
27
+ if opts.respond_to?(:symbolize_keys!)
28
+ opts.symbolize_keys!
29
+ else
30
+ opts = symbolize_keys(opts)
31
+ end
32
+
33
+ servers = Array(
34
+ args.any? ? args.unshift : opts.delete(:servers)
35
+ ).flatten.compact
36
+
37
+ opts[:prefix_key] ||= "#{opts[:namespace]}:"
38
+
39
+ @logger = opts[:logger]
40
+ @debug = opts[:debug]
41
+
42
+ super(servers, opts)
43
+ end
44
+
45
+ def symbolize_keys(opts)
46
+ # Destructively convert all keys to symbols.
47
+ if opts.kind_of?(Hash) && !opts.kind_of?(HashWithIndifferentAccess)
48
+ opts.keys.each do |key|
49
+ unless key.is_a?(Symbol)
50
+ opts[key.to_sym] = opts[key]
51
+ opts.delete(key)
52
+ end
53
+ end
54
+ end
55
+ opts
56
+ end
57
+
58
+ def namespace
59
+ options[:prefix_key]
60
+ end
61
+
62
+ # Wraps Memcached::Rails#add to return a text string - for cache money
63
+ def add(key, value, ttl=@default_ttl, raw=false)
64
+ logger.debug("Memcached add: #{key.inspect}") if logger && @debug
65
+ super(key, value, ttl, !raw)
66
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
67
+ stored
68
+ rescue Memcached::NotStored
69
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
70
+ not_stored
71
+ rescue Memcached::Error
72
+ log_error($!) if logger
73
+ not_stored
74
+ end
75
+
76
+ def replace(key, value, ttl = @default_ttl, raw = false)
77
+ logger.debug("Memcached replace: #{key.inspect}") if logger && @debug
78
+ super(key, value, ttl, !raw)
79
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
80
+ stored
81
+ rescue Memcached::NotStored
82
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
83
+ not_stored
84
+ rescue Memcached::Error
85
+ log_error($!) if logger
86
+ not_stored
87
+ end
88
+
89
+ # Wraps Memcached#get so that it doesn't raise. This has the side-effect of preventing you from
90
+ # storing <tt>nil</tt> values.
91
+ def get(key, raw=false)
92
+ logger.debug("Memcached get: #{key.inspect}") if logger && @debug
93
+ value = super(key, !raw)
94
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
95
+ value
96
+ rescue Memcached::NotFound
97
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
98
+ nil
99
+ rescue TypeError
100
+ log_error($!) if logger
101
+ delete(key)
102
+ logger.debug("Memcached deleted: #{key.inspect}") if logger && @debug
103
+ nil
104
+ rescue Memcached::Error
105
+ log_error($!) if logger
106
+ nil
107
+ end
108
+
109
+ def fetch(key, expiry = 0, raw = false)
110
+ value = get(key, !raw)
111
+
112
+ if value.nil? && block_given?
113
+ value = yield
114
+ add(key, value, expiry, !raw)
115
+ end
116
+
117
+ value
118
+ end
119
+
120
+ # Wraps Memcached#cas so that it doesn't raise. Doesn't set anything if no value is present.
121
+ def cas(key, ttl=@default_ttl, raw=false, &block)
122
+ logger.debug("Memcached cas: #{key.inspect}") if logger && @debug
123
+ super(key, ttl, !raw, &block)
124
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
125
+ stored
126
+ rescue Memcached::NotFound
127
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
128
+ rescue TypeError
129
+ log_error($!) if logger
130
+ delete(key)
131
+ logger.debug("Memcached deleted: #{key.inspect}") if logger && @debug
132
+ rescue Memcached::Error
133
+ if $!.is_a?(Memcached::ClientError)
134
+ raise $!
135
+ end
136
+ log_error($!) if logger
137
+ end
138
+
139
+ def get_multi(*keys)
140
+ keys.flatten!
141
+ logger.debug("Memcached get_multi: #{keys.inspect}") if logger && @debug
142
+ values = super(keys, true)
143
+ logger.debug("Memcached hit: #{keys.inspect}") if logger && @debug
144
+ values
145
+ rescue Memcached::NotFound
146
+ logger.debug("Memcached miss: #{keys.inspect}") if logger && @debug
147
+ {}
148
+ rescue TypeError
149
+ log_error($!) if logger
150
+ keys.each { |key| delete(key) }
151
+ logger.debug("Memcached deleted: #{keys.inspect}") if logger && @debug
152
+ {}
153
+ rescue Memcached::Error
154
+ log_error($!) if logger
155
+ {}
156
+ end
157
+
158
+ def set(key, value, ttl=@default_ttl, raw=false)
159
+ logger.debug("Memcached set: #{key.inspect}") if logger && @debug
160
+ super(key, value, ttl, !raw)
161
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
162
+ stored
163
+ rescue Memcached::Error
164
+ log_error($!) if logger
165
+ not_stored
166
+ end
167
+
168
+ def append(key, value)
169
+ logger.debug("Memcached append: #{key.inspect}") if logger && @debug
170
+ super(key, value)
171
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
172
+ stored
173
+ rescue Memcached::NotStored
174
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
175
+ not_stored
176
+ rescue Memcached::Error
177
+ log_error($!) if logger
178
+ end
179
+
180
+ def prepend(key, value)
181
+ logger.debug("Memcached prepend: #{key.inspect}") if logger && @debug
182
+ super(key, value)
183
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
184
+ stored
185
+ rescue Memcached::NotStored
186
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
187
+ not_stored
188
+ rescue Memcached::Error
189
+ log_error($!) if logger
190
+ end
191
+
192
+ def delete(key)
193
+ logger.debug("Memcached delete: #{key.inspect}") if logger && @debug
194
+ super(key)
195
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
196
+ deleted
197
+ rescue Memcached::NotFound
198
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
199
+ not_found
200
+ rescue Memcached::Error
201
+ log_error($!) if logger
202
+ end
203
+
204
+ def incr(*args)
205
+ super
206
+ rescue Memcached::NotFound
207
+ rescue Memcached::Error
208
+ log_error($!) if logger
209
+ end
210
+
211
+ def decr(*args)
212
+ super
213
+ rescue Memcached::NotFound
214
+ rescue Memcached::Error
215
+ log_error($!) if logger
216
+ end
217
+
218
+ def get_server_for_key(key, options = {})
219
+ server_by_key(key)
220
+ end
221
+
222
+ alias :reset :quit
223
+ alias :close :quit #nodoc
224
+ alias :flush_all :flush
225
+ alias :compare_and_swap :cas
226
+ alias :"[]" :get
227
+ alias :"[]=" :set
228
+
229
+ private
230
+
231
+ def stored
232
+ "STORED\r\n"
233
+ end
234
+
235
+ def deleted
236
+ "DELETED\r\n"
237
+ end
238
+
239
+ def not_stored
240
+ "NOT_STORED\r\n"
241
+ end
242
+
243
+ def not_found
244
+ "NOT_FOUND\r\n"
245
+ end
246
+
247
+ def log_error(err)
248
+ #logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
249
+ logger.error("Memcached ERROR, #{err.class}: #{err}") if logger
250
+ end
251
+
252
+ end
253
+ ####### they have MemCache installed (don't need the wrapper)
254
+ elsif defined? MemCache
255
+
256
+ Rails.logger.info("cache-money: MemCache installed") if defined? Rails
257
+ #TODO add logging?
258
+ class MemcachedWrapper < ::MemCache
259
+ end
260
+
261
+ else
262
+ Rails.logger.warn 'unable to determine memcache implementation' if defined? Rails
263
+ end #include the wraper
data/rails/init.rb ADDED
@@ -0,0 +1,38 @@
1
+ yml = YAML.load(IO.read(File.join(RAILS_ROOT, "config", "memcached.yml")))
2
+ memcache_config = yml[RAILS_ENV]
3
+ memcache_config.symbolize_keys! if memcache_config.respond_to?(:symbolize_keys!)
4
+
5
+ memcache_config[:logger] = Rails.logger
6
+ memcache_servers =
7
+ case memcache_config[:servers].class.to_s
8
+ when "String"; memcache_config[:servers].gsub(' ', '').split(',')
9
+ when "Array"; memcache_config[:servers]
10
+ end
11
+
12
+ Rails.logger.info '$memcache enabled.'
13
+
14
+ $memcache = MemcachedWrapper.new(memcache_servers, memcache_config)
15
+
16
+ if defined?(DISABLE_CACHE_MONEY) || ENV['DISABLE_CACHE_MONEY'] == 'true' || memcache_config.nil? || memcache_config[:cache_money] != true
17
+ Rails.logger.info 'cache-money disabled'
18
+ class ActiveRecord::Base
19
+ def self.index(*args)
20
+ end
21
+ end
22
+ else
23
+ Rails.logger.info 'cache-money enabled'
24
+ require 'cache_money'
25
+
26
+ ActionController::Base.session_options[:cache] = $memcache if memcache_config[:sessions]
27
+ $local = Cash::Local.new($memcache)
28
+ $lock = Cash::Lock.new($memcache)
29
+ $cache = Cash::Transactional.new($local, $lock)
30
+
31
+ # allow setting up caching on a per-model basis
32
+ unless memcache_config[:automatic_caching].to_s == 'false'
33
+ Rails.logger.info "cache-money: global model caching enabled"
34
+ class ActiveRecord::Base
35
+ is_cached(:repository => $cache)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,159 @@
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
+ end
22
+
23
+ describe '#get' do
24
+ describe '#get("...")' do
25
+ describe 'when there is a cache miss' do
26
+ it 'returns the value of the block' do
27
+ Story.get("yabba") { "dabba" }.should == "dabba"
28
+ end
29
+
30
+ it 'adds to the cache' do
31
+ Story.get("yabba") { "dabba" }
32
+ Story.get("yabba").should == "dabba"
33
+ end
34
+ end
35
+
36
+ describe 'when there is a cache hit' do
37
+ before do
38
+ Story.set("yabba", "dabba")
39
+ end
40
+
41
+ it 'returns the value of the cache' do
42
+ Story.get("yabba") { "doo" }.should == "dabba"
43
+ end
44
+
45
+ it 'does nothing to the cache' do
46
+ Story.get("yabba") { "doo" }
47
+ Story.get("yabba").should == "dabba"
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#get([...])' do
53
+ it_should_behave_like "#fetch([...])"
54
+ end
55
+ end
56
+
57
+ describe '#add' do
58
+ describe 'when the value already exists' do
59
+ describe 'when a block is given' do
60
+ it 'yields to the block' do
61
+ Story.set("count", 1)
62
+ Story.add("count", 1) { "yield me" }.should == "yield me"
63
+ end
64
+ end
65
+
66
+ describe 'when no block is given' do
67
+ it 'does not error' do
68
+ Story.set("count", 1)
69
+ lambda { Story.add("count", 1) }.should_not raise_error
70
+ end
71
+ end
72
+ end
73
+
74
+ describe 'when the value does not already exist' do
75
+ it 'adds the key to the cache' do
76
+ Story.add("count", 1)
77
+ Story.get("count").should == 1
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#set' do
83
+ end
84
+
85
+ describe '#incr' do
86
+ describe 'when there is a cache hit' do
87
+ before do
88
+ Story.set("count", 0, :raw => true)
89
+ end
90
+
91
+ it 'increments the value of the cache' do
92
+ Story.incr("count", 2)
93
+ Story.get("count", :raw => true).should =~ /2/
94
+ end
95
+
96
+ it 'returns the new cache value' do
97
+ Story.incr("count", 2).should == 2
98
+ end
99
+ end
100
+
101
+ describe 'when there is a cache miss' do
102
+ it 'initializes the value of the cache to the value of the block' do
103
+ Story.incr("count", 1) { 5 }
104
+ Story.get("count", :raw => true).should =~ /5/
105
+ end
106
+
107
+ it 'returns the new cache value' do
108
+ Story.incr("count", 1) { 2 }.should == 2
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#decr' do
114
+ describe 'when there is a cache hit' do
115
+ before do
116
+ Story.incr("count", 1) { 10 }
117
+ end
118
+
119
+ it 'decrements the value of the cache' do
120
+ Story.decr("count", 2)
121
+ Story.get("count", :raw => true).should =~ /8/
122
+ end
123
+
124
+ it 'returns the new cache value' do
125
+ Story.decr("count", 2).should == 8
126
+ end
127
+ end
128
+
129
+ describe 'when there is a cache miss' do
130
+ it 'initializes the value of the cache to the value of the block' do
131
+ Story.decr("count", 1) { 5 }
132
+ Story.get("count", :raw => true).should =~ /5/
133
+ end
134
+
135
+ it 'returns the new cache value' do
136
+ Story.decr("count", 1) { 2 }.should == 2
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#expire' do
142
+ it 'deletes the key' do
143
+ Story.set("bobo", 1)
144
+ Story.expire("bobo")
145
+ Story.get("bobo").should == nil
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,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