AdoccaMemcache 0.1.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.
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: