artemk-cache-money 0.2.13.2

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 (48) 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 +12 -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 +86 -0
  11. data/lib/cash/accessor.rb +83 -0
  12. data/lib/cash/buffered.rb +129 -0
  13. data/lib/cash/config.rb +82 -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 +197 -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/active_record.rb +5 -0
  27. data/lib/cash/util/array.rb +9 -0
  28. data/lib/cash/util/marshal.rb +19 -0
  29. data/lib/cash/write_through.rb +69 -0
  30. data/lib/mem_cached_session_store.rb +50 -0
  31. data/lib/mem_cached_support_store.rb +141 -0
  32. data/lib/memcached_wrapper.rb +261 -0
  33. data/spec/cash/accessor_spec.rb +186 -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 +67 -0
  37. data/spec/cash/finders_spec.rb +408 -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 +172 -0
  43. data/spec/cash/transactional_spec.rb +578 -0
  44. data/spec/cash/window_spec.rb +195 -0
  45. data/spec/cash/write_through_spec.rb +245 -0
  46. data/spec/memcached_wrapper_test.rb +209 -0
  47. data/spec/spec_helper.rb +68 -0
  48. metadata +168 -0
@@ -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,5 @@
1
+ class ActiveRecord::Base
2
+ def <=>(other)
3
+ self.id.to_i <=> other.id.to_i
4
+ end
5
+ 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,50 @@
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 Exception => e
48
+ # Memcached wasn't available so neither can the store be. Reraise any other error.
49
+ raise unless e.is_a?(LoadError) && e.message =~ /memcached/
50
+ end
@@ -0,0 +1,141 @@
1
+ begin
2
+ require 'memcached'
3
+
4
+ # A cache store implementation which stores data in Memcached:
5
+ # http://www.danga.com/memcached/
6
+ #
7
+ # This is currently the most popular cache store for production websites.
8
+ #
9
+ # Special features:
10
+ # - Clustering and load balancing. One can specify multiple memcached servers,
11
+ # and MemCacheStore will load balance between all available servers. If a
12
+ # server goes down, then MemCacheStore will ignore it until it goes back
13
+ # online.
14
+ # - Time-based expiry support. See #write and the +:expires_in+ option.
15
+ # - Per-request in memory cache for all communication with the MemCache server(s).
16
+ class MemCachedSupportStore < ActiveSupport::Cache::Store
17
+
18
+ attr_reader :addresses
19
+
20
+ # Creates a new MemCacheStore object, with the given memcached server
21
+ # addresses. Each address is either a host name, or a host-with-port string
22
+ # in the form of "host_name:port". For example:
23
+ #
24
+ # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
25
+ #
26
+ # If no addresses are specified, then MemCacheStore will connect to
27
+ # localhost port 11211 (the default memcached port).
28
+ def initialize(*addresses)
29
+ addresses = addresses.flatten
30
+ options = addresses.extract_options!
31
+ options[:prefix_key] ||= options[:namespace]
32
+ addresses = ["localhost"] if addresses.empty?
33
+ @addresses = addresses
34
+ @data = Memcached.new(addresses, options)
35
+
36
+ extend ActiveSupport::Cache::Strategy::LocalCache
37
+ end
38
+
39
+ def read(key, options = nil) # :nodoc:
40
+ super
41
+ @data.get(key, marshal?(options))
42
+ rescue Memcached::NotFound
43
+ nil
44
+ rescue Memcached::Error => e
45
+ logger.error("MemcachedError (#{e}): #{e.message}")
46
+ nil
47
+ end
48
+
49
+ # Writes a value to the cache.
50
+ #
51
+ # Possible options:
52
+ # - +:unless_exist+ - set to true if you don't want to update the cache
53
+ # if the key is already set.
54
+ # - +:expires_in+ - the number of seconds that this value may stay in
55
+ # the cache. See ActiveSupport::Cache::Store#write for an example.
56
+ def write(key, value, options = nil)
57
+ super
58
+ method = options && options[:unless_exist] ? :add : :set
59
+ # memcache-client will break the connection if you send it an integer
60
+ # in raw mode, so we convert it to a string to be sure it continues working.
61
+ @data.send(method, key, value, expires_in(options), marshal?(options))
62
+ true
63
+ rescue Memcached::NotStored
64
+ false
65
+ rescue Memcached::NotFound
66
+ false
67
+ rescue Memcached::Error => e
68
+ logger.error("MemcachedError (#{e}): #{e.message}")
69
+ false
70
+ end
71
+
72
+ def delete(key, options = nil) # :nodoc:
73
+ super
74
+ @data.delete(key)
75
+ true
76
+ rescue Memcached::NotFound
77
+ false
78
+ rescue Memcached::Error => e
79
+ logger.error("MemcachedError (#{e}): #{e.message}")
80
+ false
81
+ end
82
+
83
+ def exist?(key, options = nil) # :nodoc:
84
+ # Doesn't call super, cause exist? in memcache is in fact a read
85
+ # But who cares? Reading is very fast anyway
86
+ # Local cache is checked first, if it doesn't know then memcache itself is read from
87
+ !read(key, options).nil?
88
+ end
89
+
90
+ def increment(key, amount = 1) # :nodoc:
91
+ log("incrementing", key, amount)
92
+
93
+ @data.incr(key, amount)
94
+ response
95
+ rescue Memcached::NotFound
96
+ nil
97
+ rescue Memcached::Error
98
+ nil
99
+ end
100
+
101
+ def decrement(key, amount = 1) # :nodoc:
102
+ log("decrement", key, amount)
103
+ @data.decr(key, amount)
104
+ response
105
+ rescue Memcached::NotFound
106
+ nil
107
+ rescue Memcached::Error
108
+ nil
109
+ end
110
+
111
+ def delete_matched(matcher, options = nil) # :nodoc:
112
+ # don't do any local caching at present, just pass
113
+ # through and let the error happen
114
+ super
115
+ raise "Not supported by Memcache"
116
+ end
117
+
118
+ def clear
119
+ @data.flush
120
+ rescue Memcached::NotFound
121
+ end
122
+
123
+ def stats
124
+ @data.stats
125
+ rescue Memcached::NotFound
126
+ end
127
+
128
+ private
129
+ def expires_in(options)
130
+ (options && options[:expires_in]) || 0
131
+ end
132
+
133
+ def marshal?(options)
134
+ !(options && options[:raw])
135
+ end
136
+ end
137
+
138
+ rescue Exception => e
139
+ # Memcached wasn't available so neither can the store be. Reraise any other error.
140
+ raise unless e.is_a?(LoadError) && e.message =~ /memcached/
141
+ end
@@ -0,0 +1,261 @@
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 Memcached installed (do need the wrapper)
11
+ if defined? Memcached
12
+ Rails.logger.info("cache-money: Memcached installed") if defined? Rails
13
+
14
+ class Memcached
15
+ alias :get_multi :get #:nodoc:
16
+ end
17
+
18
+ class MemcachedWrapper < ::Memcached
19
+ DEFAULTS = { :servers => '127.0.0.1:11211' }
20
+
21
+ attr_reader :logger, :default_ttl
22
+
23
+ # See Memcached#new for details.
24
+ def initialize(*args)
25
+ opts = DEFAULTS.merge(args.last.is_a?(Hash) ? args.pop : {})
26
+
27
+ if opts.respond_to?(:symbolize_keys!)
28
+ opts.symbolize_keys!
29
+ else
30
+ opts = symbolize_keys(opts)
31
+ end
32
+
33
+ servers = Array(
34
+ args.any? ? args.unshift : opts.delete(:servers)
35
+ ).flatten.compact
36
+
37
+ opts[:prefix_key] ||= "#{opts[:namespace]}:"
38
+
39
+ @logger = opts[:logger]
40
+ @debug = opts[:debug]
41
+
42
+ super(servers, opts)
43
+ end
44
+
45
+ def symbolize_keys(opts)
46
+ # Destructively convert all keys to symbols.
47
+ if opts.kind_of?(Hash) && !opts.kind_of?(HashWithIndifferentAccess)
48
+ opts.keys.each do |key|
49
+ unless key.is_a?(Symbol)
50
+ opts[key.to_sym] = opts[key]
51
+ opts.delete(key)
52
+ end
53
+ end
54
+ end
55
+ opts
56
+ end
57
+
58
+ def namespace
59
+ options[:prefix_key]
60
+ end
61
+
62
+ # Wraps Memcached::Rails#add to return a text string - for cache money
63
+ def add(key, value, ttl=@default_ttl, raw=false)
64
+ logger.debug("Memcached add: #{key.inspect}") if logger && @debug
65
+ super(key, value, ttl, !raw)
66
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
67
+ stored
68
+ rescue Memcached::NotStored
69
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
70
+ not_stored
71
+ rescue Memcached::Error
72
+ log_error($!) if logger
73
+ not_stored
74
+ end
75
+
76
+ def replace(key, value, ttl = @default_ttl, raw = false)
77
+ logger.debug("Memcached replace: #{key.inspect}") if logger && @debug
78
+ super(key, value, ttl, !raw)
79
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
80
+ stored
81
+ rescue Memcached::NotStored
82
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
83
+ not_stored
84
+ rescue Memcached::Error
85
+ log_error($!) if logger
86
+ not_stored
87
+ end
88
+
89
+ # Wraps Memcached#get so that it doesn't raise. This has the side-effect of preventing you from
90
+ # storing <tt>nil</tt> values.
91
+ def get(key, raw=false)
92
+ logger.debug("Memcached get: #{key.inspect}") if logger && @debug
93
+ value = super(key, !raw)
94
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
95
+ value
96
+ rescue Memcached::NotFound
97
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
98
+ nil
99
+ rescue TypeError
100
+ log_error($!) if logger
101
+ delete(key)
102
+ logger.debug("Memcached deleted: #{key.inspect}") if logger && @debug
103
+ nil
104
+ rescue Memcached::Error
105
+ log_error($!) if logger
106
+ nil
107
+ end
108
+
109
+ def fetch(key, expiry = 0, raw = false)
110
+ value = get(key, !raw)
111
+
112
+ if value.nil? && block_given?
113
+ value = yield
114
+ add(key, value, expiry, !raw)
115
+ end
116
+
117
+ value
118
+ end
119
+
120
+ # Wraps Memcached#cas so that it doesn't raise. Doesn't set anything if no value is present.
121
+ def cas(key, ttl=@default_ttl, raw=false, &block)
122
+ logger.debug("Memcached cas: #{key.inspect}") if logger && @debug
123
+ super(key, ttl, !raw, &block)
124
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
125
+ stored
126
+ rescue Memcached::NotFound
127
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
128
+ rescue TypeError
129
+ log_error($!) if logger
130
+ delete(key)
131
+ logger.debug("Memcached deleted: #{key.inspect}") if logger && @debug
132
+ rescue Memcached::Error
133
+ if $!.is_a?(Memcached::ClientError)
134
+ raise $!
135
+ end
136
+ log_error($!) if logger
137
+ end
138
+
139
+ def get_multi(*keys)
140
+ keys.flatten!
141
+ logger.debug("Memcached get_multi: #{keys.inspect}") if logger && @debug
142
+ values = super(keys, true)
143
+ logger.debug("Memcached hit: #{keys.inspect}") if logger && @debug
144
+ values
145
+ rescue Memcached::NotFound
146
+ logger.debug("Memcached miss: #{keys.inspect}") if logger && @debug
147
+ {}
148
+ rescue TypeError
149
+ log_error($!) if logger
150
+ keys.each { |key| delete(key) }
151
+ logger.debug("Memcached deleted: #{keys.inspect}") if logger && @debug
152
+ {}
153
+ rescue Memcached::Error
154
+ log_error($!) if logger
155
+ {}
156
+ end
157
+
158
+ def set(key, value, ttl=@default_ttl, raw=false)
159
+ logger.debug("Memcached set: #{key.inspect}") if logger && @debug
160
+ super(key, value, ttl, !raw)
161
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
162
+ stored
163
+ rescue Memcached::Error
164
+ log_error($!) if logger
165
+ not_stored
166
+ end
167
+
168
+ def append(key, value)
169
+ logger.debug("Memcached append: #{key.inspect}") if logger && @debug
170
+ super(key, value)
171
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
172
+ stored
173
+ rescue Memcached::NotStored
174
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
175
+ not_stored
176
+ rescue Memcached::Error
177
+ log_error($!) if logger
178
+ end
179
+
180
+ def prepend(key, value)
181
+ logger.debug("Memcached prepend: #{key.inspect}") if logger && @debug
182
+ super(key, value)
183
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
184
+ stored
185
+ rescue Memcached::NotStored
186
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
187
+ not_stored
188
+ rescue Memcached::Error
189
+ log_error($!) if logger
190
+ end
191
+
192
+ def delete(key)
193
+ logger.debug("Memcached delete: #{key.inspect}") if logger && @debug
194
+ super(key)
195
+ logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
196
+ deleted
197
+ rescue Memcached::NotFound
198
+ logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
199
+ not_found
200
+ rescue Memcached::Error
201
+ log_error($!) if logger
202
+ end
203
+
204
+ def incr(*args)
205
+ super
206
+ rescue Memcached::NotFound
207
+ rescue Memcached::Error
208
+ log_error($!) if logger
209
+ end
210
+
211
+ def decr(*args)
212
+ super
213
+ rescue Memcached::NotFound
214
+ rescue Memcached::Error
215
+ log_error($!) if logger
216
+ end
217
+
218
+ def get_server_for_key(key, options = {})
219
+ server_by_key(key)
220
+ end
221
+
222
+ alias :reset :quit
223
+ alias :close :quit #nodoc
224
+ alias :flush_all :flush
225
+ alias :compare_and_swap :cas
226
+ alias :"[]" :get
227
+ alias :"[]=" :set
228
+
229
+ private
230
+
231
+ def stored
232
+ "STORED\r\n"
233
+ end
234
+
235
+ def deleted
236
+ "DELETED\r\n"
237
+ end
238
+
239
+ def not_stored
240
+ "NOT_STORED\r\n"
241
+ end
242
+
243
+ def not_found
244
+ "NOT_FOUND\r\n"
245
+ end
246
+
247
+ def log_error(err)
248
+ #logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
249
+ logger.error("Memcached ERROR, #{err.class}: #{err}") if logger
250
+ end
251
+
252
+ end
253
+ ####### they have MemCache installed (don't need the wrapper)
254
+ elsif defined? MemCache
255
+ Rails.logger.info("cache-money: MemCache installed") if defined? Rails
256
+ #TODO add logging?
257
+ class MemcachedWrapper < ::MemCache
258
+ end
259
+ else
260
+ Rails.logger.warn 'unable to determine memcache implementation' if defined? Rails
261
+ end #include the wraper