sreeix-cache-money 0.2.24.1

Sign up to get free protection for your applications and to get access to all the features.
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