ngmoco-cache-money 0.2.10 → 0.2.13
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/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)
|