sreeix-cache-money 0.2.24.1

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.
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,210 @@
1
+ module Cash
2
+ module Query
3
+ class Abstract
4
+ delegate :with_exclusive_scope, :get, :table_name, :indices, :find_from_ids_without_cache, :cache_key, :columns_hash, :logger, :to => :@active_record
5
+
6
+ def self.perform(*args)
7
+ new(*args).perform
8
+ end
9
+
10
+ def initialize(active_record, options1, options2)
11
+ @active_record, @options1, @options2 = active_record, options1, options2 || {}
12
+ end
13
+
14
+ def perform(find_options = {}, get_options = {})
15
+ if cache_config = cacheable?(@options1, @options2, find_options)
16
+ cache_keys, index = cache_keys(cache_config[0]), cache_config[1]
17
+
18
+ misses, missed_keys, objects = hit_or_miss(cache_keys, index, get_options)
19
+ format_results(cache_keys, choose_deserialized_objects_if_possible(missed_keys, cache_keys, misses, objects))
20
+ else
21
+ logger.debug(" \e[1;4;31mUNCACHEABLE\e[0m #{table_name} - #{find_options.inspect} - #{get_options.inspect} - #{@options1.inspect} - #{@options2.inspect}") if logger
22
+ uncacheable
23
+ end
24
+ end
25
+
26
+ DESC = /DESC/i
27
+
28
+ def order
29
+ @order ||= begin
30
+ if order_sql = @options1[:order] || @options2[:order]
31
+ matched, table_name, column_name, direction = *(ORDER.match(order_sql.to_s))
32
+ [column_name, direction =~ DESC ? :desc : :asc]
33
+ else
34
+ ['id', :asc]
35
+ end
36
+ end
37
+ rescue TypeError
38
+ ['id', :asc]
39
+ end
40
+
41
+ def limit
42
+ @limit ||= @options1[:limit] || @options2[:limit]
43
+ end
44
+
45
+ def offset
46
+ @offset ||= @options1[:offset] || @options2[:offset] || 0
47
+ end
48
+
49
+ def calculation?
50
+ false
51
+ end
52
+
53
+ private
54
+ def cacheable?(*optionss)
55
+ if @active_record.respond_to?(:cacheable?) && ! @active_record.cacheable?(*optionss)
56
+ if logger
57
+ if @active_record.respond_to?(:cacheable?)
58
+ logger.debug(" \e[1;4;31mUNCACHEABLE CLASS\e[0m #{table_name}")
59
+ else
60
+ logger.debug(" \e[1;4;31mUNCACHEABLE INSTANCE\e[0m #{table_name} - #{optionss.inspect}")
61
+ end
62
+ end
63
+ return false
64
+ end
65
+ optionss.each do |options|
66
+ unless safe_options_for_cache?(options)
67
+ logger.debug(" \e[1;4;31mUNCACHEABLE UNSAFE\e[0m #{table_name} - #{options.inspect}") if logger
68
+ return false
69
+ end
70
+ end
71
+ partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
72
+ return if partial_indices.include?(nil)
73
+ attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
74
+
75
+ # attribute_value_pairs.each do |attribute_value_pair|
76
+ # return false if attribute_value_pair.last.is_a?(Array)
77
+ # end
78
+
79
+ if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
80
+ if index.matches?(self)
81
+ [attribute_value_pairs, index]
82
+ else
83
+ logger.debug(" \e[1;4;31mUNCACHEABLE NO MATCHING INDEX\e[0m #{table_name} - #{index.order_column.inspect} #{index.order.inspect} #{index.limit.inspect}") if logger
84
+ false
85
+ end
86
+ else
87
+ logger.debug(" \e[1;4;31mUNCACHEABLE NOT INDEXED\e[0m #{table_name} - #{attribute_value_pairs.collect { |pair| pair[0] }.inspect}") if logger
88
+ false
89
+ end
90
+ end
91
+
92
+ def hit_or_miss(cache_keys, index, options)
93
+ misses, missed_keys = nil, nil
94
+ objects = @active_record.get(cache_keys, options.merge(:ttl => index.ttl)) do |missed_keys|
95
+ misses = miss(missed_keys, @options1.merge(:limit => index.window))
96
+ serialize_objects(index, misses)
97
+ end
98
+ [misses, missed_keys, objects]
99
+ end
100
+
101
+ def cache_keys(attribute_value_pairs)
102
+ attribute_value_pairs.flatten.join('/')
103
+ end
104
+
105
+ def safe_options_for_cache?(options)
106
+ return false unless options.kind_of?(Hash)
107
+ options.except(:conditions, :readonly, :limit, :offset, :order).values.compact.empty? && !options[:readonly]
108
+ end
109
+
110
+ def attribute_value_pairs_for_conditions(conditions)
111
+ case conditions
112
+ when Hash
113
+ conditions.to_a.collect { |key, value| [key.to_s, value] }
114
+ when String
115
+ parse_indices_from_condition(conditions.gsub('1 = 1 AND ', '')) #ignore unnecessary conditions
116
+ when Array
117
+ parse_indices_from_condition(*conditions)
118
+ when NilClass
119
+ []
120
+ end
121
+ end
122
+
123
+ AND = /\s+AND\s+/i
124
+ TABLE_AND_COLUMN = /(?:(?:`|")?(\w+)(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
125
+ VALUE = /'?(\d+|\?|(?:(?:[^']|'')*))'?/ # Matches: 123, ?, '123', '12''3'
126
+ KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s+=\s+#{VALUE}\)?$/ # Matches: KEY = VALUE, (KEY = VALUE)
127
+ ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i # Matches: COLUMN ASC, COLUMN DESC, COLUMN
128
+
129
+ def parse_indices_from_condition(conditions = '', *values)
130
+ values = values.dup
131
+ conditions.split(AND).inject([]) do |indices, condition|
132
+ matched, table_name, column_name, sql_value = *(KEY_EQ_VALUE.match(condition))
133
+ if matched
134
+ # value = sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
135
+ if sql_value == '?'
136
+ value = values.shift
137
+ else
138
+ column = columns_hash[column_name]
139
+ raise "could not find column #{column_name} in columns #{columns_hash.keys.join(',')}" if column.nil?
140
+ if sql_value[0..0] == ':' && values && values.count > 0 && values[0].is_a?(Hash)
141
+ symb = sql_value[1..-1].to_sym
142
+ value = column.type_cast(values[0][symb])
143
+ else
144
+ value = column.type_cast(sql_value)
145
+ end
146
+ end
147
+ indices << [column_name, value]
148
+ else
149
+ return nil
150
+ end
151
+ end
152
+ end
153
+
154
+ def indexed_on?(attributes)
155
+ indices.detect { |index| index == attributes }
156
+ rescue NoMethodError
157
+ nil
158
+ end
159
+ alias_method :index_for, :indexed_on?
160
+
161
+ def format_results(cache_keys, objects)
162
+ return objects if objects.blank?
163
+
164
+ objects = convert_to_array(cache_keys, objects)
165
+ objects = apply_limits_and_offsets(objects, @options1)
166
+ deserialize_objects(objects)
167
+ end
168
+
169
+ def choose_deserialized_objects_if_possible(missed_keys, cache_keys, misses, objects)
170
+ missed_keys == cache_keys ? misses : objects
171
+ end
172
+
173
+ def serialize_objects(index, objects)
174
+ Array(objects).collect { |missed| index.serialize_object(missed) }
175
+ end
176
+
177
+ def convert_to_array(cache_keys, object)
178
+ if object.kind_of?(Hash)
179
+ cache_keys.collect { |key| object[cache_key(key)] }.flatten.compact
180
+ else
181
+ Array(object)
182
+ end
183
+ end
184
+
185
+ def apply_limits_and_offsets(results, options)
186
+ results.slice((options[:offset] || 0), (options[:limit] || results.length))
187
+ end
188
+
189
+ def deserialize_objects(objects)
190
+ if objects.first.kind_of?(ActiveRecord::Base)
191
+ objects
192
+ else
193
+ cache_keys = objects.collect { |id| "id/#{id}" }
194
+ with_exclusive_scope(:find => {}) { objects = get(cache_keys, &method(:find_from_keys)) }
195
+ convert_to_array(cache_keys, objects)
196
+ end
197
+ end
198
+
199
+ def find_from_keys(*missing_keys)
200
+ missing_ids = Array(missing_keys).flatten.collect { |key| key.split('/')[2].to_i }
201
+ options = {}
202
+ order_sql = @options1[:order] || @options2[:order]
203
+ options[:order] = order_sql if order_sql
204
+ results = find_from_ids_without_cache(missing_ids, options)
205
+ results.each {|o| @active_record.add_to_caches(o) } if results && results.is_a?(Array)
206
+ results
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,45 @@
1
+ module Cash
2
+ module Query
3
+ class Calculation < Abstract
4
+ delegate :calculate_without_cache, :incr, :to => :@active_record
5
+
6
+ def initialize(active_record, operation, column, options1, options2)
7
+ super(active_record, options1, options2)
8
+ @operation, @column = operation, column
9
+ end
10
+
11
+ def perform
12
+ super({}, :raw => true)
13
+ end
14
+
15
+ def calculation?
16
+ true
17
+ end
18
+
19
+ protected
20
+ def miss(_, __)
21
+ calculate_without_cache(@operation, @column, @options1)
22
+ end
23
+
24
+ def uncacheable
25
+ calculate_without_cache(@operation, @column, @options1)
26
+ end
27
+
28
+ def format_results(_, objects)
29
+ objects.to_i
30
+ end
31
+
32
+ def serialize_objects(_, objects)
33
+ objects.to_s
34
+ end
35
+
36
+ def cacheable?(*optionss)
37
+ @column == :all && super(*optionss)
38
+ end
39
+
40
+ def cache_keys(attribute_value_pairs)
41
+ "#{super(attribute_value_pairs)}/#{@operation}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ module Cash
2
+ module Query
3
+ class PrimaryKey < Abstract
4
+ def initialize(active_record, ids, options1, options2)
5
+ super(active_record, options1, options2)
6
+ @expects_array = ids.first.kind_of?(Array)
7
+ @original_ids = ids
8
+ @ids = ids.flatten.compact.uniq.collect do |object|
9
+ object.respond_to?(:quoted_id) ? object.quoted_id : object.to_i
10
+ end
11
+ end
12
+
13
+ def perform
14
+ return [] if @expects_array && @ids.empty?
15
+ raise ActiveRecord::RecordNotFound if @ids.empty?
16
+
17
+ super(:conditions => { :id => @ids.first })
18
+ end
19
+
20
+ protected
21
+ def deserialize_objects(objects)
22
+ convert_to_active_record_collection(super(objects))
23
+ end
24
+
25
+ def cache_keys(attribute_value_pairs)
26
+ @ids.collect { |id| "id/#{id}" }
27
+ end
28
+
29
+ def miss(missing_keys, options)
30
+ find_from_keys(*missing_keys)
31
+ end
32
+
33
+ def uncacheable
34
+ find_from_ids_without_cache(@original_ids, @options1)
35
+ end
36
+
37
+ private
38
+ def convert_to_active_record_collection(objects)
39
+ case objects.size
40
+ when 0
41
+ raise ActiveRecord::RecordNotFound
42
+ when 1
43
+ @expects_array ? objects : objects.first
44
+ else
45
+ objects
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ module Cash
2
+ module Query
3
+ class Select < Abstract
4
+ delegate :find_every_without_cache, :to => :@active_record
5
+
6
+ protected
7
+ def miss(_, miss_options)
8
+ find_every_without_cache(miss_options)
9
+ end
10
+
11
+ def uncacheable
12
+ find_every_without_cache(@options1)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Cash
2
+ Request = {}
3
+ end
@@ -0,0 +1,43 @@
1
+ module Cash
2
+ class Transactional
3
+ attr_reader :memcache
4
+
5
+ def initialize(memcache, lock)
6
+ @memcache, @cache = [memcache, memcache]
7
+ @lock = lock
8
+ end
9
+
10
+ def transaction
11
+ exception_was_raised = false
12
+ begin_transaction
13
+ result = yield
14
+ rescue Object => e
15
+ exception_was_raised = true
16
+ raise
17
+ ensure
18
+ begin
19
+ @cache.flush unless exception_was_raised
20
+ ensure
21
+ end_transaction
22
+ end
23
+ end
24
+
25
+ def respond_to?(method)
26
+ @cache.respond_to?(method)
27
+ end
28
+
29
+ private
30
+
31
+ def method_missing(method, *args, &block)
32
+ @cache.send(method, *args, &block)
33
+ end
34
+
35
+ def begin_transaction
36
+ @cache = Buffered.push(@cache, @lock)
37
+ end
38
+
39
+ def end_transaction
40
+ @cache = @cache.pop
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ class Array
2
+ alias_method :count, :size
3
+
4
+ def to_hash_without_nils
5
+ keys_and_values_without_nils = reject { |key, value| value.nil? }
6
+ shallow_flattened_keys_and_values_without_nils = keys_and_values_without_nils.inject([]) { |result, pair| result += pair }
7
+ Hash[*shallow_flattened_keys_and_values_without_nils]
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Marshal
2
+ class << self
3
+ def constantize(name)
4
+ name.constantize
5
+ end
6
+
7
+ def load_with_constantize(value)
8
+ begin
9
+ Marshal.load_without_constantize value
10
+ rescue ArgumentError => e
11
+ _, class_name = *(/undefined class\/module ([\w:]*\w)/.match(e.message))
12
+ raise if !class_name
13
+ constantize(class_name)
14
+ Marshal.load value
15
+ end
16
+ end
17
+ alias_method_chain :load, :constantize
18
+ end
19
+ end
@@ -0,0 +1,69 @@
1
+ module Cash
2
+ module WriteThrough
3
+ DEFAULT_TTL = 12.hours
4
+
5
+ def self.included(active_record_class)
6
+ active_record_class.class_eval do
7
+ include InstanceMethods
8
+ extend ClassMethods
9
+ end
10
+ end
11
+
12
+ module InstanceMethods
13
+ def self.included(active_record_class)
14
+ active_record_class.class_eval do
15
+ after_create :add_to_caches
16
+ after_update :update_caches
17
+ after_destroy :remove_from_caches
18
+ end
19
+ end
20
+
21
+ def add_to_caches
22
+ InstanceMethods.unfold(self.class, :add_to_caches, self)
23
+ end
24
+
25
+ def update_caches
26
+ InstanceMethods.unfold(self.class, :update_caches, self)
27
+ end
28
+
29
+ def remove_from_caches
30
+ return if new_record?
31
+ InstanceMethods.unfold(self.class, :remove_from_caches, self)
32
+ end
33
+
34
+ def expire_caches
35
+ InstanceMethods.unfold(self.class, :expire_caches, self)
36
+ end
37
+
38
+ def shallow_clone
39
+ self.class.send(:instantiate, instance_variable_get(:@attributes))
40
+ end
41
+
42
+ private
43
+ def self.unfold(klass, operation, object)
44
+ while klass < ActiveRecord::Base && klass.ancestors.include?(WriteThrough)
45
+ klass.send(operation, object)
46
+ klass = klass.superclass
47
+ end
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+ def add_to_caches(object)
53
+ indices.each { |index| index.add(object) } if cache_config
54
+ end
55
+
56
+ def update_caches(object)
57
+ indices.each { |index| index.update(object) } if cache_config
58
+ end
59
+
60
+ def remove_from_caches(object)
61
+ indices.each { |index| index.remove(object) } if cache_config
62
+ end
63
+
64
+ def expire_caches(object)
65
+ indices.each { |index| index.delete(object) } if cache_config
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,49 @@
1
+ # begin
2
+ require 'memcached'
3
+
4
+ class MemCachedSessionStore < ActionController::Session::AbstractStore
5
+ def initialize(app, options = {})
6
+ # Support old :expires option
7
+ options[:expire_after] ||= options[:expires]
8
+
9
+ super
10
+
11
+ @default_options = {
12
+ :namespace => 'rack:session',
13
+ :servers => 'localhost:11211'
14
+ }.merge(@default_options)
15
+
16
+ @default_options[:prefix_key] ||= @default_options[:namespace]
17
+
18
+ @pool = options[:cache] || Memcached.new(@default_options[:servers], @default_options)
19
+ # unless @pool.servers.any? { |s| s.alive? }
20
+ # raise "#{self} unable to find server during initialization."
21
+ # end
22
+ @mutex = Mutex.new
23
+
24
+ super
25
+ end
26
+
27
+ private
28
+ def get_session(env, sid)
29
+ sid ||= generate_sid
30
+ begin
31
+ session = @pool.get(sid) || {}
32
+ rescue Memcached::NotFound, MemCache::MemCacheError, Errno::ECONNREFUSED
33
+ session = {}
34
+ end
35
+ [sid, session]
36
+ end
37
+
38
+ def set_session(env, sid, session_data)
39
+ options = env['rack.session.options']
40
+ expiry = options[:expire_after] || 0
41
+ @pool.set(sid, session_data, expiry)
42
+ return true
43
+ rescue Memcached::NotStored, MemCache::MemCacheError, Errno::ECONNREFUSED
44
+ return false
45
+ end
46
+ end
47
+ # rescue LoadError
48
+ # # Memcached wasn't available so neither can the store be
49
+ # end
@@ -0,0 +1,135 @@
1
+ require 'memcached'
2
+
3
+ # A cache store implementation which stores data in Memcached:
4
+ # http://www.danga.com/memcached/
5
+ #
6
+ # This is currently the most popular cache store for production websites.
7
+ #
8
+ # Special features:
9
+ # - Clustering and load balancing. One can specify multiple memcached servers,
10
+ # and MemCacheStore will load balance between all available servers. If a
11
+ # server goes down, then MemCacheStore will ignore it until it goes back
12
+ # online.
13
+ # - Time-based expiry support. See #write and the +:expires_in+ option.
14
+ # - Per-request in memory cache for all communication with the MemCache server(s).
15
+ class MemCachedSupportStore < ActiveSupport::Cache::Store
16
+
17
+ attr_reader :addresses
18
+
19
+ # Creates a new MemCacheStore object, with the given memcached server
20
+ # addresses. Each address is either a host name, or a host-with-port string
21
+ # in the form of "host_name:port". For example:
22
+ #
23
+ # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
24
+ #
25
+ # If no addresses are specified, then MemCacheStore will connect to
26
+ # localhost port 11211 (the default memcached port).
27
+ def initialize(*addresses)
28
+ addresses = addresses.flatten
29
+ options = addresses.extract_options!
30
+ options[:prefix_key] ||= options[:namespace]
31
+ addresses = ["localhost"] if addresses.empty?
32
+ @addresses = addresses
33
+ @data = Memcached.new(addresses, options)
34
+
35
+ extend ActiveSupport::Cache::Strategy::LocalCache
36
+ end
37
+
38
+ def read(key, options = nil) # :nodoc:
39
+ super
40
+ @data.get(key, marshal?(options))
41
+ rescue Memcached::NotFound
42
+ nil
43
+ rescue Memcached::Error => e
44
+ logger.error("MemcachedError (#{e}): #{e.message}")
45
+ nil
46
+ end
47
+
48
+ # Writes a value to the cache.
49
+ #
50
+ # Possible options:
51
+ # - +:unless_exist+ - set to true if you don't want to update the cache
52
+ # if the key is already set.
53
+ # - +:expires_in+ - the number of seconds that this value may stay in
54
+ # the cache. See ActiveSupport::Cache::Store#write for an example.
55
+ def write(key, value, options = nil)
56
+ super
57
+ method = options && options[:unless_exist] ? :add : :set
58
+ # memcache-client will break the connection if you send it an integer
59
+ # in raw mode, so we convert it to a string to be sure it continues working.
60
+ @data.send(method, key, value, expires_in(options), marshal?(options))
61
+ true
62
+ rescue Memcached::NotStored
63
+ false
64
+ rescue Memcached::NotFound
65
+ false
66
+ rescue Memcached::Error => e
67
+ logger.error("MemcachedError (#{e}): #{e.message}")
68
+ false
69
+ end
70
+
71
+ def delete(key, options = nil) # :nodoc:
72
+ super
73
+ @data.delete(key)
74
+ true
75
+ rescue Memcached::NotFound
76
+ false
77
+ rescue Memcached::Error => e
78
+ logger.error("MemcachedError (#{e}): #{e.message}")
79
+ false
80
+ end
81
+
82
+ def exist?(key, options = nil) # :nodoc:
83
+ # Doesn't call super, cause exist? in memcache is in fact a read
84
+ # But who cares? Reading is very fast anyway
85
+ # Local cache is checked first, if it doesn't know then memcache itself is read from
86
+ !read(key, options).nil?
87
+ end
88
+
89
+ def increment(key, amount = 1) # :nodoc:
90
+ log("incrementing", key, amount)
91
+
92
+ @data.incr(key, amount)
93
+ response
94
+ rescue Memcached::NotFound
95
+ nil
96
+ rescue Memcached::Error
97
+ nil
98
+ end
99
+
100
+ def decrement(key, amount = 1) # :nodoc:
101
+ log("decrement", key, amount)
102
+ @data.decr(key, amount)
103
+ response
104
+ rescue Memcached::NotFound
105
+ nil
106
+ rescue Memcached::Error
107
+ nil
108
+ end
109
+
110
+ def delete_matched(matcher, options = nil) # :nodoc:
111
+ # don't do any local caching at present, just pass
112
+ # through and let the error happen
113
+ super
114
+ raise "Not supported by Memcache"
115
+ end
116
+
117
+ def clear
118
+ @data.flush
119
+ rescue Memcached::NotFound
120
+ end
121
+
122
+ def stats
123
+ @data.stats
124
+ rescue Memcached::NotFound
125
+ end
126
+
127
+ private
128
+ def expires_in(options)
129
+ (options && options[:expires_in] && options[:expires_in].to_i) || 0
130
+ end
131
+
132
+ def marshal?(options)
133
+ !(options && options[:raw])
134
+ end
135
+ end