af-cache-money 0.2.10

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