ngmoco-cache-money 0.2.10 → 0.2.13
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +201 -0
- data/README +16 -0
- data/README.markdown +210 -0
- data/config/environment.rb +12 -2
- data/db/schema.rb +6 -0
- data/lib/cache_money.rb +38 -9
- data/lib/cash/buffered.rb +7 -4
- data/lib/cash/config.rb +6 -3
- data/lib/cash/index.rb +6 -1
- data/lib/cash/local.rb +10 -6
- data/lib/cash/lock.rb +11 -4
- data/lib/cash/mock.rb +154 -0
- data/lib/cash/query/abstract.rb +14 -6
- data/lib/cash/query/primary_key.rb +0 -1
- data/lib/cash/transactional.rb +5 -4
- data/lib/cash/write_through.rb +5 -8
- data/lib/mem_cached_session_store.rb +49 -0
- data/lib/mem_cached_support_store.rb +135 -0
- data/lib/memcached_wrapper.rb +3 -2
- data/rails/init.rb +12 -11
- data/spec/cash/accessor_spec.rb +39 -27
- data/spec/cash/active_record_spec.rb +14 -1
- data/spec/cash/buffered_spec.rb +9 -0
- data/spec/cash/calculations_spec.rb +1 -1
- data/spec/cash/finders_spec.rb +38 -2
- data/spec/cash/local_buffer_spec.rb +9 -0
- data/spec/cash/local_spec.rb +9 -0
- data/spec/cash/lock_spec.rb +3 -4
- data/spec/cash/order_spec.rb +1 -1
- data/spec/cash/transactional_spec.rb +16 -12
- data/spec/cash/window_spec.rb +1 -1
- data/spec/cash/write_through_spec.rb +1 -1
- data/spec/memcached_wrapper_test.rb +209 -0
- data/spec/spec_helper.rb +6 -3
- metadata +53 -24
data/lib/cash/write_through.rb
CHANGED
@@ -36,10 +36,7 @@ module Cash
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def shallow_clone
|
39
|
-
|
40
|
-
clone.instance_variable_set("@attributes", instance_variable_get(:@attributes))
|
41
|
-
clone.instance_variable_set("@new_record", new_record?)
|
42
|
-
clone
|
39
|
+
self.class.send(:instantiate, instance_variable_get(:@attributes))
|
43
40
|
end
|
44
41
|
|
45
42
|
private
|
@@ -53,19 +50,19 @@ module Cash
|
|
53
50
|
|
54
51
|
module ClassMethods
|
55
52
|
def add_to_caches(object)
|
56
|
-
indices.each { |index| index.add(object) }
|
53
|
+
indices.each { |index| index.add(object) } if cache_config
|
57
54
|
end
|
58
55
|
|
59
56
|
def update_caches(object)
|
60
|
-
indices.each { |index| index.update(object) }
|
57
|
+
indices.each { |index| index.update(object) } if cache_config
|
61
58
|
end
|
62
59
|
|
63
60
|
def remove_from_caches(object)
|
64
|
-
indices.each { |index| index.remove(object) }
|
61
|
+
indices.each { |index| index.remove(object) } if cache_config
|
65
62
|
end
|
66
63
|
|
67
64
|
def expire_caches(object)
|
68
|
-
indices.each { |index| index.delete(object) }
|
65
|
+
indices.each { |index| index.delete(object) } if cache_config
|
69
66
|
end
|
70
67
|
end
|
71
68
|
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]) || 0
|
130
|
+
end
|
131
|
+
|
132
|
+
def marshal?(options)
|
133
|
+
!(options && options[:raw])
|
134
|
+
end
|
135
|
+
end
|
data/lib/memcached_wrapper.rb
CHANGED
@@ -26,7 +26,7 @@ end
|
|
26
26
|
class MemcachedWrapper < ::Memcached
|
27
27
|
DEFAULTS = { :servers => '127.0.0.1:11211' }
|
28
28
|
|
29
|
-
attr_reader :logger
|
29
|
+
attr_reader :logger, :default_ttl
|
30
30
|
|
31
31
|
# See Memcached#new for details.
|
32
32
|
def initialize(*args)
|
@@ -253,7 +253,8 @@ private
|
|
253
253
|
end
|
254
254
|
|
255
255
|
def log_error(err)
|
256
|
-
logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
|
256
|
+
#logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
|
257
|
+
logger.error("Memcached ERROR, #{err.class}: #{err}") if logger
|
257
258
|
end
|
258
259
|
|
259
260
|
end
|
data/rails/init.rb
CHANGED
@@ -2,7 +2,7 @@ yml = YAML.load(IO.read(File.join(RAILS_ROOT, "config", "memcached.yml")))
|
|
2
2
|
memcache_config = yml[RAILS_ENV]
|
3
3
|
memcache_config.symbolize_keys! if memcache_config.respond_to?(:symbolize_keys!)
|
4
4
|
|
5
|
-
if defined?(DISABLE_CACHE_MONEY) || memcache_config.nil? || memcache_config[:cache_money] != true
|
5
|
+
if defined?(DISABLE_CACHE_MONEY) || ENV['DISABLE_CACHE_MONEY'] == 'true' || memcache_config.nil? || memcache_config[:cache_money] != true
|
6
6
|
Rails.logger.info 'cache-money disabled'
|
7
7
|
class ActiveRecord::Base
|
8
8
|
def self.index(*args)
|
@@ -13,7 +13,12 @@ else
|
|
13
13
|
require 'cache_money'
|
14
14
|
|
15
15
|
memcache_config[:logger] = Rails.logger
|
16
|
-
|
16
|
+
memcache_servers =
|
17
|
+
case memcache_config[:servers].class.to_s
|
18
|
+
when "String"; memcache_config[:servers].gsub(' ', '').split(',')
|
19
|
+
when "Array"; memcache_config[:servers]
|
20
|
+
end
|
21
|
+
$memcache = MemcachedWrapper.new(memcache_servers, memcache_config)
|
17
22
|
|
18
23
|
#ActionController::Base.cache_store = :cache_money_mem_cache_store
|
19
24
|
ActionController::Base.session_options[:cache] = $memcache if memcache_config[:sessions]
|
@@ -25,15 +30,11 @@ else
|
|
25
30
|
$lock = Cash::Lock.new($memcache)
|
26
31
|
$cache = Cash::Transactional.new($local, $lock)
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
0
|
34
|
-
else
|
35
|
-
self.id < other.id ? -1 : 1
|
36
|
-
end
|
33
|
+
# allow setting up caching on a per-model basis
|
34
|
+
unless memcache_config[:automatic_caching].to_s == 'false'
|
35
|
+
Rails.logger.info "cache-money: global model caching enabled"
|
36
|
+
class ActiveRecord::Base
|
37
|
+
is_cached(:repository => $cache)
|
37
38
|
end
|
38
39
|
end
|
39
40
|
end
|
data/spec/cash/accessor_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
|
+
require 'ruby-debug'
|
2
3
|
|
3
4
|
module Cash
|
4
5
|
describe Accessor do
|
@@ -80,10 +81,38 @@ module Cash
|
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
84
|
+
describe '#add' do
|
85
|
+
describe 'when the value already exists' do
|
86
|
+
describe 'when a block is given' do
|
87
|
+
it 'yields to the block' do
|
88
|
+
Story.set("count", 1)
|
89
|
+
Story.add("count", 1) { "yield me" }.should == "yield me"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'when no block is given' do
|
94
|
+
it 'does not error' do
|
95
|
+
Story.set("count", 1)
|
96
|
+
lambda { Story.add("count", 1) }.should_not raise_error
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'when the value does not already exist' do
|
102
|
+
it 'adds the key to the cache' do
|
103
|
+
Story.add("count", 1)
|
104
|
+
Story.get("count").should == 1
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#set' do
|
110
|
+
end
|
111
|
+
|
83
112
|
describe '#incr' do
|
84
113
|
describe 'when there is a cache hit' do
|
85
114
|
before do
|
86
|
-
Story.set("count", 0)
|
115
|
+
Story.set("count", 0, :raw => true)
|
87
116
|
end
|
88
117
|
|
89
118
|
it 'increments the value of the cache' do
|
@@ -108,31 +137,6 @@ module Cash
|
|
108
137
|
end
|
109
138
|
end
|
110
139
|
|
111
|
-
describe '#add' do
|
112
|
-
describe 'when the value already exists' do
|
113
|
-
describe 'when a block is given' do
|
114
|
-
it 'yields to the block' do
|
115
|
-
Story.set("count", 1)
|
116
|
-
Story.add("count", 1) { "yield me" }.should == "yield me"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
describe 'when no block is given' do
|
121
|
-
it 'does not error' do
|
122
|
-
Story.set("count", 1)
|
123
|
-
lambda { Story.add("count", 1) }.should_not raise_error
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
describe 'when the value does not already exist' do
|
129
|
-
it 'adds the key to the cache' do
|
130
|
-
Story.add("count", 1)
|
131
|
-
Story.get("count").should == 1
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
140
|
describe '#decr' do
|
137
141
|
describe 'when there is a cache hit' do
|
138
142
|
before do
|
@@ -161,6 +165,14 @@ module Cash
|
|
161
165
|
end
|
162
166
|
end
|
163
167
|
|
168
|
+
describe '#expire' do
|
169
|
+
it 'deletes the key' do
|
170
|
+
Story.set("bobo", 1)
|
171
|
+
Story.expire("bobo")
|
172
|
+
Story.get("bobo").should == nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
164
176
|
describe '#cache_key' do
|
165
177
|
it 'uses the version number' do
|
166
178
|
Story.version 1
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
module Cash
|
4
4
|
describe Finders do
|
@@ -207,5 +207,18 @@ module Cash
|
|
207
207
|
end
|
208
208
|
end
|
209
209
|
end
|
210
|
+
|
211
|
+
describe 'loading' do
|
212
|
+
it "should be able to create a record for an ar subclass that was loaded before cache money" do
|
213
|
+
$debug = true
|
214
|
+
session = ActiveRecord::SessionStore::Session.new
|
215
|
+
session.session_id = "12345"
|
216
|
+
session.data = "foobarbaz"
|
217
|
+
|
218
|
+
lambda {
|
219
|
+
session.save!
|
220
|
+
}.should_not raise_error
|
221
|
+
end
|
222
|
+
end
|
210
223
|
end
|
211
224
|
end
|
data/spec/cash/finders_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
3
|
module Cash
|
4
4
|
describe Finders do
|
@@ -339,10 +339,20 @@ module Cash
|
|
339
339
|
end
|
340
340
|
|
341
341
|
describe '#find_by_attr' do
|
342
|
+
before(:each) do
|
343
|
+
Story.find_by_title(@story.title) # populates cache for title with [@story.id]
|
344
|
+
end
|
345
|
+
|
342
346
|
it 'populates the cache' do
|
343
|
-
Story.find_by_title(@story.title)
|
344
347
|
Story.fetch("title/#{@story.title}").should == [@story.id]
|
345
348
|
end
|
349
|
+
|
350
|
+
it 'populates the cache when finding by non-primary-key attribute' do
|
351
|
+
Story.find_by_title(@story.title) # populates cache for id with record
|
352
|
+
|
353
|
+
mock(Story.connection).execute.never # should hit cache only
|
354
|
+
Story.find_by_title(@story.title).id.should == @story.id
|
355
|
+
end
|
346
356
|
end
|
347
357
|
|
348
358
|
describe '#find(:all, :conditions => ...)' do
|
@@ -352,6 +362,32 @@ module Cash
|
|
352
362
|
end
|
353
363
|
end
|
354
364
|
|
365
|
+
describe '#find(:all, :conditions => ..., :order => ...)' do
|
366
|
+
before(:each) do
|
367
|
+
@short1 = Short.create(:title => 'title',
|
368
|
+
:subtitle => 'subtitle')
|
369
|
+
@short2 = Short.create(:title => 'another title',
|
370
|
+
:subtitle => 'subtitle')
|
371
|
+
$memcache.flush_all
|
372
|
+
Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
|
373
|
+
:order => 'title')
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'populates the cache' do
|
377
|
+
Short.fetch("subtitle/subtitle").should_not be_blank
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'returns objects in the correct order' do
|
381
|
+
Short.fetch("subtitle/subtitle").should ==
|
382
|
+
[@short2.id, @short1.id]
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'finds objects in the correct order' do
|
386
|
+
Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
|
387
|
+
:order => 'title').map(&:id).should == [@short2.id, @short1.id]
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
355
391
|
describe '#find(1)' do
|
356
392
|
it 'populates the cache' do
|
357
393
|
Story.find(@story.id)
|