AdoccaMemcache 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ == 1.0.0 / 2006-11-13
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
5
+
data/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/adocca_memcache
6
+ lib/adocca_memcache.rb
7
+ lib/am_memcache.rb
8
+ lib/am_action_view.rb
9
+ lib/am_caching.rb
10
+ lib/am_memcache_store.rb
11
+ lib/am_memcache_memoize.rb
12
+ test/test_adocca_memcache.rb
data/README.txt ADDED
@@ -0,0 +1,40 @@
1
+ AdoccaMemcache
2
+ by Adocca AB
3
+ http://rubyforge.org/projects/adocca-plugins
4
+
5
+ == DESCRIPTION:
6
+
7
+ A client library to simplify usage of memcached within Ruby on Rails projects.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * FIX (list of features or problems)
12
+
13
+ == SYNOPSYS:
14
+
15
+ FIX (code sample of usage)
16
+
17
+ == REQUIREMENTS:
18
+
19
+ * FIX (list of requirements)
20
+
21
+ == INSTALL:
22
+
23
+ * FIX (sudo gem install, anything else)
24
+
25
+ == LICENSE:
26
+
27
+ This program is free software; you can redistribute it and/or
28
+ modify it under the terms of the GNU General Public License
29
+ as published by the Free Software Foundation; either version 2
30
+ of the License, or (at your option) any later version.
31
+
32
+ This program is distributed in the hope that it will be useful,
33
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
34
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
+ GNU General Public License for more details.
36
+
37
+ You should have received a copy of the GNU General Public License
38
+ along with this program; if not, write to the Free Software
39
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
40
+
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/adocca_memcache.rb'
6
+
7
+ Hoe.new('AdoccaMemcache', '0.1.0') do |p|
8
+ p.rubyforge_name = 'adocca-plugins'
9
+ p.summary = 'A client library to simplify using memcached with Ruby on Rails projects.'
10
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
11
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
12
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
13
+ end
14
+
15
+ # vim: syntax=Ruby
File without changes
@@ -0,0 +1,16 @@
1
+ # Include hook code here
2
+
3
+ $: << File.dirname(__FILE__)
4
+
5
+ require_gem 'rails'
6
+ require 'am_action_view.rb'
7
+ require 'am_memcache.rb'
8
+ require 'am_memcache_store.rb'
9
+ require 'am_caching.rb'
10
+ require 'am_memcache_memoize.rb'
11
+
12
+ ActiveRecord::Base.send(:include, MemcacheMemoize)
13
+ ActiveRecord::Base.send(:extend, MemcacheMemoize)
14
+ ActionView::Base.send(:include, MemcacheMemoize)
15
+ ActionController::Base.send(:include, MemcacheMemoize)
16
+ ActionController::Base.send(:extend, MemcacheMemoize)
@@ -0,0 +1,6 @@
1
+ # Customized the Rails own error handling
2
+ ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
3
+ msg = instance.error_message
4
+ title = msg.kind_of?(Array) ? '* ' + msg.join("\n* ") : msg
5
+ "<span class=\"field-with-errors\">#{html_tag}</span>"
6
+ end
data/lib/am_caching.rb ADDED
@@ -0,0 +1,28 @@
1
+ module ActionView
2
+ module Helpers
3
+ module CacheHelper
4
+ def cache_value_erb(basename, expiry=0, args={}, &block)
5
+ @controller.cache_value_erb(basename, expiry, args, &block)
6
+ end
7
+ end
8
+ end
9
+ end
10
+ module ActionController
11
+ module Caching
12
+ module Fragments
13
+ def cache_value_erb(basename, expiry=0, args={}, &block)
14
+ buffer = eval("_erbout", block.binding)
15
+
16
+ if cache = cache_read(basename,args)
17
+ buffer.concat(cache)
18
+ else
19
+ pos = buffer.length
20
+ block.call
21
+ cache_value(basename, expiry, args) do
22
+ buffer[pos..-1]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,552 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require 'digest/sha1'
4
+ require 'timeout'
5
+
6
+ module Adocca
7
+
8
+ #
9
+ # Extend this module to be able to magically synchronize on any object, just like in Java(tm)!
10
+ #
11
+ module Synchronized
12
+ def synchronize(&block)
13
+ raise "Adocca::Synchronized::CACHE is not defined!" unless defined?(Adocca::Synchronized::CACHE)
14
+ Adocca::Synchronized::CACHE.synchronize(self.inspect) do
15
+ yield
16
+ end
17
+ end
18
+ end
19
+
20
+ class NullMutex
21
+ def synchronize(&block)
22
+ block.call
23
+ end
24
+ end
25
+
26
+ #
27
+ # A Ruby client library for memcached. Ripped from the gem.
28
+ #
29
+ class MemCache
30
+
31
+ # a semi-unique id for this ruby instance
32
+ HOST_HASH = Array.new(32).collect do |e| (65 + rand(25)).chr end.join
33
+
34
+ # Default options for the cache object.
35
+ DEFAULT_OPTIONS = {
36
+ :namespace => nil,
37
+ :readonly => false
38
+ }
39
+
40
+ # Default memcached port.
41
+ DEFAULT_PORT = 11211
42
+
43
+ # Default memcached server weight.
44
+ DEFAULT_WEIGHT = 1
45
+
46
+ # The amount of time to wait for a response from a memcached server. If a
47
+ # response is not completed within this time, the connection to the server
48
+ # will be closed and an error will be raised.
49
+ attr_accessor :request_timeout
50
+
51
+ # Valid options are:
52
+ #
53
+ # :namespace
54
+ # If specified, all keys will have the given value prepended
55
+ # before accessing the cache. Defaults to nil.
56
+ #
57
+ # :readonly
58
+ # If this is set, any attempt to write to the cache will generate
59
+ # an exception. Defaults to false.
60
+ #
61
+ def initialize(opts = {})
62
+ opts = DEFAULT_OPTIONS.merge(opts)
63
+ @namespace = opts[:namespace]
64
+ @readonly = opts[:readonly]
65
+ if ActionController::Base.allow_concurrency
66
+ @mutex = Mutex.new
67
+ else
68
+ @mutex = NullMutex.new
69
+ end
70
+ @servers = []
71
+ @buckets = []
72
+ end
73
+
74
+ # Return a string representation of the cache object.
75
+ def inspect
76
+ sprintf("<MemCache: %s servers, %s buckets, ns: %p, ro: %p>",
77
+ @servers.nitems, @buckets.nitems, @namespace, @readonly)
78
+ end
79
+
80
+ # Returns whether there is at least one active server for the object.
81
+ def active?
82
+ not @servers.empty?
83
+ end
84
+
85
+ # Returns whether the cache was created read only.
86
+ def readonly?
87
+ @readonly
88
+ end
89
+
90
+ # Set the servers that the requests will be distributed between. Entries
91
+ # can be either strings of the form "hostname:port" or
92
+ # "hostname:port:weight" or MemCache::Server objects.
93
+ def servers=(servers)
94
+ # Create the server objects.
95
+ @servers = servers.collect do |server|
96
+ case server
97
+ when String
98
+ host, port, weight = server.split(/:/, 3)
99
+ port ||= DEFAULT_PORT
100
+ weight ||= DEFAULT_WEIGHT
101
+ Server::new(host, port, weight)
102
+ when Server
103
+ server
104
+ else
105
+ raise TypeError, "Cannot convert %s to MemCache::Server" %
106
+ svr.class.name
107
+ end
108
+ end
109
+
110
+ # Create an array of server buckets for weight selection of servers.
111
+ @buckets = []
112
+ @servers.each do |server|
113
+ server.weight.times { @buckets.push(server) }
114
+ end
115
+ end
116
+
117
+ # get an entry from the cache
118
+ def get(key)
119
+ cache_key = make_cache_key(key)
120
+ rval = nil
121
+ @mutex.synchronize do
122
+ begin
123
+ response, sock, server = sock_send_command(cache_key, "get #{cache_key}")
124
+ return :MemCache_no_such_entry if response =~ /^END/
125
+
126
+ v, cache_key, flags, bytes = response.split(/ /)
127
+ rval = sock.read(bytes.to_i)
128
+
129
+ sock.gets
130
+ sock.gets
131
+ rescue SystemCallError, IOError => err
132
+ server.close
133
+ raise MemCacheError, err.message
134
+ end
135
+ end
136
+
137
+ if rval.strip.match(/^\d+$/)
138
+ rval.strip.to_i
139
+ else
140
+ # Return the unmarshaled value.
141
+ begin
142
+ Marshal.load(rval)
143
+ rescue ArgumentError, TypeError => err
144
+ raise MemCacheError, err.message
145
+ end
146
+ end
147
+ end
148
+
149
+ #
150
+ # Invalidate a namespace in the cache.
151
+ #
152
+ def invalidate_namespace(ns)
153
+ inc(make_namespace_key(ns).last)
154
+ end
155
+
156
+ #
157
+ # Increment an entry in the cache
158
+ #
159
+ def inc(key, amount = 1)
160
+ cache_key = make_cache_key(key)
161
+ rval = nil
162
+ @mutex.synchronize do
163
+ rval = send_command(cache_key, "incr #{cache_key} #{amount}")
164
+ end
165
+ rval = rval.strip.to_i if rval =~ /^\d+/
166
+ rval
167
+ end
168
+
169
+ #
170
+ # Increment an entry in the cache
171
+ #
172
+ def dec(key, amount = 1)
173
+ cache_key = make_cache_key(key)
174
+ rval = nil
175
+ @mutex.synchronize do
176
+ rval = send_command(cache_key, "decr #{cache_key} #{amount}")
177
+ end
178
+ rval = rval.strip.to_i if rval =~ /^\d+/
179
+ rval
180
+ end
181
+
182
+ # the response when the key is present
183
+ ADD_NOTSTORED = "NOT_STORED\r\n"
184
+ # the response when the key is NOT present
185
+ ADD_STORED = "STORED\r\n"
186
+
187
+ # Add an entry to the cache
188
+ # will return true if the entry didnt already exist
189
+ def add(key, value, expiry = 0)
190
+ cache_key = make_cache_key(key)
191
+ marshaled_value = nil
192
+ if Integer === value
193
+ marshaled_value = value.to_s
194
+ else
195
+ marshaled_value = Marshal.dump(value)
196
+ end
197
+ @mutex.synchronize do
198
+ send_command(cache_key, "add #{cache_key} 0 #{expiry} #{marshaled_value.size}\r\n" + marshaled_value) == ADD_STORED
199
+ end
200
+ end
201
+
202
+ # Add an entry to the cache.
203
+ def set(key, value, expiry = 0)
204
+ cache_key = make_cache_key(key)
205
+ marshaled_value = nil
206
+ if Integer === value
207
+ marshaled_value = value.to_s
208
+ else
209
+ marshaled_value = Marshal.dump(value)
210
+ end
211
+ @mutex.synchronize do
212
+ send_command(cache_key, "set #{cache_key} 0 #{expiry} #{marshaled_value.size}\r\n" + marshaled_value)
213
+ end
214
+ end
215
+
216
+ # Remove an entry from the cache.
217
+ # return true if it is deleted
218
+ def delete(key, expiry = 0)
219
+ cache_key = make_cache_key(key)
220
+ @mutex.synchronize do
221
+ send_command(cache_key, "delete #{cache_key} #{expiry}")
222
+ end
223
+ end
224
+
225
+ # Reset the connection to all memcache servers. This should be called if
226
+ # there is a problem with a cache lookup that might have left the
227
+ # connection in a corrupted state.
228
+ def reset
229
+ @mutex.synchronize do
230
+ @servers.each { |server| server.close }
231
+ end
232
+ end
233
+
234
+ # Method to check if a value is set, and NOT unmarshal, only return bool
235
+ def check(key)
236
+ cache_key = make_cache_key(key)
237
+ @mutex.synchronize do
238
+
239
+ begin
240
+ response, sock, server = sock_send_command(cache_key, "get #{cache_key}")
241
+
242
+ return false if response =~ /^END/
243
+
244
+ v, key, flags, bytes = response.split(/ /)
245
+ value = sock.read(bytes.to_i)
246
+ sock.gets
247
+ sock.gets
248
+ rescue SystemCallError, IOError => err
249
+ server.close
250
+ raise MemCacheError, err.message
251
+ end
252
+
253
+ true
254
+ end
255
+ end
256
+
257
+ # Flush cache
258
+ def flush
259
+ @mutex.synchronize do
260
+ raise MemCacheError, "No active servers" unless self.active?
261
+ @servers.each do |server|
262
+
263
+ sock = server.socket
264
+ if sock.nil?
265
+ raise MemCacheError, "No connection to server"
266
+ end
267
+
268
+ begin
269
+ sock.write "flush_all\r\n"
270
+ sock.gets
271
+ rescue SystemCallError, IOError => err
272
+ server.close
273
+ raise MemCacheError, err.message
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ # lock expiry time
280
+ TRANS_EXPTIME = 10
281
+ TRANS_MAX_TRIES = 10
282
+
283
+ # memcache-driven transaction mechanism
284
+ def synchronize(key)
285
+ cache_key = make_cache_key(key)
286
+ tries = 0
287
+ we_locked_now = false
288
+ we_locked_earlier = false
289
+ ok_value = "#{HOST_HASH}:#{Thread.current.object_id}"
290
+ while (!(we_locked_now = add(cache_key, ok_value, TRANS_EXPTIME)) &&
291
+ !(we_locked_earlier = (get(cache_key) == ok_value)) &&
292
+ tries < TRANS_MAX_TRIES)
293
+ sleep 1
294
+ tries += 1
295
+ end
296
+ if we_locked_now || we_locked_earlier
297
+ yield
298
+ self.delete(cache_key) if we_locked_now
299
+ else
300
+ raise MemCacheError, "Couldn't obtain lock"
301
+ end
302
+ end
303
+
304
+ # Shortcut to get a value from the cache.
305
+ def [](key)
306
+ self.get(key)
307
+ end
308
+
309
+ # Shortcut to save a value in the cache. This method does not set an
310
+ # expiration on the entry. Use set to specify an explicit expiry.
311
+ def []=(key, value)
312
+ self.set(key, value)
313
+ end
314
+
315
+ ###########################################################################
316
+ # S E R V E R C L A S S
317
+ ###########################################################################
318
+
319
+ # This class represents a memcached server instance.
320
+ class Server
321
+ # The amount of time to wait to establish a connection with a
322
+ # memcached server. If a connection cannot be established within
323
+ # this time limit, the server will be marked as down.
324
+ CONNECT_TIMEOUT = 0.25
325
+
326
+ # The amount of time to wait before attempting to re-establish a
327
+ # connection with a server that is marked dead.
328
+ RETRY_DELAY = 30.0
329
+
330
+ # The host the memcached server is running on.
331
+ attr_reader :host
332
+
333
+ # The port the memcached server is listening on.
334
+ attr_reader :port
335
+
336
+ # The weight given to the server.
337
+ attr_reader :weight
338
+
339
+ # The time of next retry if the connection is dead.
340
+ attr_reader :retry
341
+
342
+ # A text status string describing the state of the server.
343
+ attr_reader :status
344
+
345
+ # Create a new MemCache::Server object for the memcached instance
346
+ # listening on the given host and port, weighted by the given weight.
347
+ def initialize(host, port = DEFAULT_PORT, weight = DEFAULT_WEIGHT)
348
+ if host.nil? || host.empty?
349
+ raise ArgumentError, "No host specified"
350
+ elsif port.nil? || port.to_i.zero?
351
+ raise ArgumentError, "No port specified"
352
+ end
353
+
354
+ @host = host
355
+ @port = port.to_i
356
+ @weight = weight.to_i
357
+
358
+ @sock = nil
359
+ @retry = nil
360
+ @status = "NOT CONNECTED"
361
+ end
362
+
363
+ # Return a string representation of the server object.
364
+ def inspect
365
+ sprintf("<MemCache::Server: %s:%d [%d] (%s)>",
366
+ @host, @port, @weight, @status)
367
+ end
368
+
369
+ # Check whether the server connection is alive. This will cause the
370
+ # socket to attempt to connect if it isn't already connected and or if
371
+ # the server was previously marked as down and the retry time has
372
+ # been exceeded.
373
+ def alive?
374
+ !self.socket.nil?
375
+ end
376
+
377
+ # Try to connect to the memcached server targeted by this object.
378
+ # Returns the connected socket object on success or nil on failure.
379
+ def socket
380
+ # Attempt to connect if not already connected.
381
+ unless @sock || (!@sock.nil? && @sock.closed?)
382
+ # If the host was dead, don't retry for a while.
383
+ if @retry && (@retry > Time::now)
384
+ @sock = nil
385
+ else
386
+ begin
387
+ @sock = timeout(CONNECT_TIMEOUT) {
388
+ TCPSocket::new(@host, @port)
389
+ }
390
+ @retry = nil
391
+ @status = "CONNECTED"
392
+ rescue SystemCallError, IOError, Timeout::Error => err
393
+ self.mark_dead(err.message)
394
+ end
395
+ end
396
+ end
397
+ @sock
398
+ end
399
+
400
+ # Close the connection to the memcached server targeted by this
401
+ # object. The server is not considered dead.
402
+ def close
403
+ @sock.close if @sock &&!@sock.closed?
404
+ @sock = nil
405
+ @retry = nil
406
+ @status = "NOT CONNECTED"
407
+ end
408
+
409
+ # Mark the server as dead and close its socket.
410
+ def mark_dead(reason = "Unknown error")
411
+ @sock.close if @sock && !@sock.closed?
412
+ @sock = nil
413
+ @retry = Time::now + RETRY_DELAY
414
+
415
+ @status = sprintf("DEAD: %s, will retry at %s", reason, @retry)
416
+ end
417
+ end
418
+
419
+
420
+ ###########################################################################
421
+ # E X C E P T I O N C L A S S E S
422
+ ###########################################################################
423
+
424
+ # Base MemCache exception class.
425
+ class MemCacheError < ::Exception
426
+ end
427
+
428
+ # MemCache internal error class. Instances of this class mean that there
429
+ # is some internal error either in the memcache client library or the
430
+ # memcached server it is talking to.
431
+ class InternalError < MemCacheError
432
+ end
433
+
434
+ # MemCache client error class. Instances of this class mean that a
435
+ # "CLIENT_ERROR" response was seen in the dialog with a memcached server.
436
+ class ClientError < InternalError
437
+ end
438
+
439
+ # MemCache server error class. Instances of this class mean that a
440
+ # "SERVER_ERROR" response was seen in the dialog with a memcached server.
441
+ class ServerError < InternalError
442
+ attr_reader :server
443
+
444
+ def initalize(server)
445
+ @server = server
446
+ end
447
+ end
448
+
449
+ # private
450
+
451
+ #
452
+ # Will send a command to the proper server for this key
453
+ # and return the response
454
+ #
455
+ def send_command(key, command)
456
+ sock_send_command(key, command)[0]
457
+ end
458
+
459
+ #
460
+ # Will send a command to the proper server for this key
461
+ # and return the response, socket and server
462
+ #
463
+ def sock_send_command(key, command)
464
+ raise MemCacheError, "No active servers" unless self.active?
465
+ raise MemCacheError, "Update of readonly cache" if @readonly
466
+ server = get_server_for_key(key)
467
+
468
+ sock = server.socket
469
+ if sock.nil?
470
+ raise MemCacheError, "No connection to server"
471
+ end
472
+
473
+ response = nil
474
+ begin
475
+ sock.write "#{command}\r\n"
476
+ [sock.gets, sock, server]
477
+ rescue SystemCallError, IOError => err
478
+ server.close
479
+ raise MemCacheError, err.message
480
+ end
481
+ end
482
+
483
+ #
484
+ # Create a key for this namespace and
485
+ # return it along with the string you want
486
+ # to delete or incr to invalidate this specific
487
+ # namespace
488
+ #
489
+ def make_namespace_key(ns, appendage = nil)
490
+ # ensure that this is an Array
491
+ ns = ns.to_a
492
+ # get the first part of the Array
493
+ next_part = ns.shift
494
+ # create a unique namespace name of it
495
+ next_part = [appendage, next_part].join(":") if appendage
496
+ # create a unique memcache key of the namespace name
497
+ next_key = "adocca:memcache:namespace:#{next_part}"
498
+ # make sure it exists in memcache
499
+ add(next_key, rand(1 << 31))
500
+ # get its value in memcache
501
+ salt = get(next_key)
502
+ if ns.empty?
503
+ # if this is the last part of the key return it + its secret salt along with itself (for invalidation purposes)
504
+ ["#{next_part}:#{salt}", next_key]
505
+ else
506
+ # otherwise, fetch the rest along with the
507
+ last_part = make_namespace_key(ns, next_part)
508
+ ["#{next_part}:#{salt}:#{last_part.first}", last_part.last]
509
+ end
510
+ end
511
+
512
+ # Create a key for the cache, incorporating the namespace qualifier if
513
+ # requested.
514
+ def make_cache_key(o)
515
+ if Array === o
516
+ o = o.clone
517
+ key = o.pop
518
+
519
+ namespace_key = ""
520
+ namespace_key << ":#{make_namespace_key(o).first}" unless o.empty?
521
+
522
+ key = "#{@namespace}#{namespace_key}:#{key}"
523
+ Digest::SHA1.new(key)
524
+ else
525
+ key = "#{@namespace}:#{o}"
526
+ Digest::SHA1.new(key)
527
+ end
528
+ end
529
+
530
+ # Pick a server to handle the request based on a hash of the key.
531
+ def get_server_for_key(key)
532
+ # Easy enough if there is only one server.
533
+ return @servers.first if @servers.length == 1
534
+
535
+ # Hash the value of the key to select the bucket.
536
+ hkey = Integer("0x#{key}")
537
+
538
+ # Fetch a server for the given key, retrying if that server is
539
+ # offline.
540
+ server = nil
541
+ 20.times do |tries|
542
+ server = @buckets[(hkey + tries) % @buckets.nitems]
543
+ break if server.alive?
544
+ end
545
+
546
+ raise MemCacheError, "No servers available" unless server
547
+ server
548
+ end
549
+
550
+
551
+ end
552
+ end
@@ -0,0 +1,110 @@
1
+ module MemcacheMemoize
2
+
3
+ # The following is used to cache, using memcache, the results of executing a code block.
4
+ # The scope of the caching is determined by the basename and the hash args. For instance:
5
+ #
6
+ # cache_value(:unread_messages, 0, :user => @user.id) do
7
+ # Messages.find(:all, :conditions => ......).size
8
+ # end
9
+ #
10
+ # will store the result of the block under the key "unread_messages:user:123" in memcache,
11
+ # for ever (expiry=0 seconds, meaning no expiration time). The number of hash pairs is
12
+ # unlimited. Duplicates will be removed.
13
+ #
14
+ def cache_value(basename, expiry=0, args={}, &cached_code)
15
+ return cached_code.call if $DISABLE_CACHE
16
+ key = compute_memcache_key(basename, args)
17
+ # Check if the value is cached
18
+ result = CACHE.get key
19
+ if result == :MemCache_no_such_entry
20
+ # No, we need to call the block and cache the result
21
+ result = cached_code.call
22
+ CACHE.set key, result, expiry
23
+ RAILS_DEFAULT_LOGGER.debug "MemCache: Computed and set value for #{key.inspect} (expiry: #{expiry} seconds)"
24
+ else
25
+ RAILS_DEFAULT_LOGGER.debug "MemCache: Got #{key.inspect}"
26
+ end
27
+ # Return the result
28
+ result
29
+ end
30
+
31
+ #
32
+ # Only read a value from memcache
33
+ #
34
+
35
+ def cache_read(basename, args={})
36
+ return nil if $DISABLE_CACHE
37
+ key = compute_memcache_key(basename, args)
38
+ result = CACHE.get key
39
+ RAILS_DEFAULT_LOGGER.debug "MemCache: Read #{key.inspect}"
40
+ if result == :MemCache_no_such_entry
41
+ nil
42
+ else
43
+ result
44
+ end
45
+ end
46
+
47
+ #
48
+ # Returns the obj, unless the obj is_a?(ActiveRecord::Base), in which case it will return
49
+ # the object without associations.
50
+ #
51
+ def without_associations(obj)
52
+ if obj.is_a?(ActiveRecord::Base)
53
+ obj.remove_associations
54
+ else
55
+ obj
56
+ end
57
+ end
58
+
59
+ #
60
+ # This method is used to expire cached data. Basename and args are as per cache_value(),
61
+ # but expiry controls in how many seconds the expiration is to take place. The default
62
+ # value of 0 means 'immediately'.
63
+ #
64
+ def expire_cached_value(basename, expiry=0, args={})
65
+ key = compute_memcache_key(basename, args)
66
+ RAILS_DEFAULT_LOGGER.debug "MemCache: Invalidating #{key.inspect}"
67
+ CACHE.delete key, expiry
68
+ end
69
+
70
+ #
71
+ # Helper method to invalidate namespaces
72
+ #
73
+ def expire_cached_namespace(namespace_string)
74
+ RAILS_DEFAULT_LOGGER.debug "MemCache: Invalidating namespace: #{namespace_string.to_s}"
75
+ CACHE.invalidate_namespace(namespace_string)
76
+ end
77
+
78
+ #
79
+ # Return true if there is such a value in the memcache, otherwise false
80
+ #
81
+ def check_cached_value(basename, args={})
82
+ key = compute_memcache_key(basename, args)
83
+ rval = CACHE.check(key)
84
+ RAILS_DEFAULT_LOGGER.debug "MemCache: Checked for #{key.inspect} - returned: " + rval.to_s
85
+ return rval
86
+ end
87
+
88
+ private
89
+
90
+ #
91
+ # Compute the memcache key string from a basename and a hash
92
+ #
93
+ def compute_memcache_key(base, args)
94
+ key_base = Array(base)
95
+ # First sort the args so that their order doesn't matter. Also eliminate duplicates.
96
+ args = (args.sort_by { |key, val| key.to_s }).uniq
97
+
98
+ # Construct and return the memcache key string
99
+ key_base[-1] = key_base.last.to_s << ":" << args.inject("") { |acc, assoc| acc + ':' + assoc[0].to_s + ':' + assoc[1].to_s } unless args.empty?
100
+
101
+ key_base
102
+ end
103
+ end
104
+
105
+
106
+ ActiveRecord::Base.send(:include, MemcacheMemoize)
107
+ ActiveRecord::Base.send(:extend, MemcacheMemoize)
108
+ ActionView::Base.send(:include, MemcacheMemoize)
109
+ ActionController::Base.send(:include, MemcacheMemoize)
110
+ ActionController::Base.send(:extend, MemcacheMemoize)
@@ -0,0 +1,13 @@
1
+
2
+ class CGI::Session::MemCacheStore
3
+ def restore
4
+ begin
5
+ @session_data = @cache[@session_key]
6
+ @session_data = {} if @session_data == :MemCache_no_such_entry
7
+ rescue
8
+ @session_data = {}
9
+ end
10
+ @session_data
11
+ end
12
+ end
13
+
File without changes
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: AdoccaMemcache
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-11-13 00:00:00 +01:00
8
+ summary: A client library to simplify using memcached with Ruby on Rails projects.
9
+ require_paths:
10
+ - lib
11
+ email: ryand-ruby@zenspider.com
12
+ homepage: " by Adocca AB"
13
+ rubyforge_project: adocca-plugins
14
+ description: "== FEATURES/PROBLEMS: * FIX (list of features or problems) == SYNOPSYS: FIX (code sample of usage) == REQUIREMENTS:"
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Ryan Davis
30
+ files:
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ - Rakefile
35
+ - bin/adocca_memcache
36
+ - lib/adocca_memcache.rb
37
+ - lib/am_memcache.rb
38
+ - lib/am_action_view.rb
39
+ - lib/am_caching.rb
40
+ - lib/am_memcache_store.rb
41
+ - lib/am_memcache_memoize.rb
42
+ - test/test_adocca_memcache.rb
43
+ test_files:
44
+ - test/test_adocca_memcache.rb
45
+ rdoc_options: []
46
+
47
+ extra_rdoc_files: []
48
+
49
+ executables:
50
+ - adocca_memcache
51
+ extensions: []
52
+
53
+ requirements: []
54
+
55
+ dependencies:
56
+ - !ruby/object:Gem::Dependency
57
+ name: hoe
58
+ version_requirement:
59
+ version_requirements: !ruby/object:Gem::Version::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 1.1.4
64
+ version: