f9a7fb144085ae0e690684bf326a8bdcb6de886d
[depot.git] / mem_cache_store.rb
1 require 'memcache'
2
3 module ActiveSupport
4 module Cache
5 # A cache store implementation which stores data in Memcached:
6 # http://www.danga.com/memcached/
7 #
8 # This is currently the most popular cache store for production websites.
9 #
10 # Special features:
11 # - Clustering and load balancing. One can specify multiple memcached servers,
12 # and MemCacheStore will load balance between all available servers. If a
13 # server goes down, then MemCacheStore will ignore it until it goes back
14 # online.
15 # - Time-based expiry support. See #write and the +:expires_in+ option.
16 class MemCacheStore < Store
17 module Response # :nodoc:
18 STORED = "STORED\r\n"
19 NOT_STORED = "NOT_STORED\r\n"
20 EXISTS = "EXISTS\r\n"
21 NOT_FOUND = "NOT_FOUND\r\n"
22 DELETED = "DELETED\r\n"
23 end
24
25 attr_reader :addresses
26
27 # Creates a new MemCacheStore object, with the given memcached server
28 # addresses. Each address is either a host name, or a host-with-port string
29 # in the form of "host_name:port". For example:
30 #
31 # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
32 #
33 # If no addresses are specified, then MemCacheStore will connect to
34 # localhost port 11211 (the default memcached port).
35 def initialize(*addresses)
36 addresses = addresses.flatten
37 options = addresses.extract_options!
38 addresses = ["localhost"] if addresses.empty?
39 @addresses = addresses
40 @data = MemCache.new(addresses, options)
41 end
42
43 def read(key, options = nil) # :nodoc:
44 super
45 @data.get(key, raw?(options))
46 rescue MemCache::MemCacheError => e
47 logger.error("MemCacheError (#{e}): #{e.message}")
48 nil
49 end
50
51 # Writes a value to the cache.
52 #
53 # Possible options:
54 # - +:unless_exist+ - set to true if you don't want to update the cache
55 # if the key is already set.
56 # - +:expires_in+ - the number of seconds that this value may stay in
57 # the cache. See ActiveSupport::Cache::Store#write for an example.
58 def write(key, value, options = nil)
59 super
60 method = options && options[:unless_exist] ? :add : :set
61 # memcache-client will break the connection if you send it an integer
62 # in raw mode, so we convert it to a string to be sure it continues working.
63 value = value.to_s if raw?(options)
64 response = @data.send(method, key, value, expires_in(options), raw?(options))
65 response == Response::STORED
66 rescue MemCache::MemCacheError => e
67 logger.error("MemCacheError (#{e}): #{e.message}")
68 false
69 end
70
71 def delete(key, options = nil) # :nodoc:
72 super
73 response = @data.delete(key, expires_in(options))
74 response == Response::DELETED
75 rescue MemCache::MemCacheError => e
76 logger.error("MemCacheError (#{e}): #{e.message}")
77 false
78 end
79
80 def exist?(key, options = nil) # :nodoc:
81 # Doesn't call super, cause exist? in memcache is in fact a read
82 # But who cares? Reading is very fast anyway
83 !read(key, options).nil?
84 end
85
86 def increment(key, amount = 1) # :nodoc:
87 log("incrementing", key, amount)
88
89 response = @data.incr(key, amount)
90 response == Response::NOT_FOUND ? nil : response
91 rescue MemCache::MemCacheError
92 nil
93 end
94
95 def decrement(key, amount = 1) # :nodoc:
96 log("decrement", key, amount)
97
98 response = @data.decr(key, amount)
99 response == Response::NOT_FOUND ? nil : response
100 rescue MemCache::MemCacheError
101 nil
102 end
103
104 def delete_matched(matcher, options = nil) # :nodoc:
105 super
106 raise "Not supported by Memcache"
107 end
108
109 def clear
110 @data.flush_all
111 end
112
113 def stats
114 @data.stats
115 end
116
117 private
118 def expires_in(options)
119 (options && options[:expires_in]) || 0
120 end
121
122 def raw?(options)
123 options && options[:raw]
124 end
125 end
126 end
127 end