ashleym1972-cache-money 0.2.6 → 0.2.7
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/config/{memcache.yml → memcached.yml} +0 -0
- data/lib/cache_money.rb +3 -3
- data/lib/cash/accessor.rb +3 -1
- data/lib/cash/buffered.rb +1 -1
- data/lib/cash/config.rb +6 -6
- data/lib/cash/fake.rb +83 -0
- data/lib/cash/index.rb +4 -6
- data/lib/cash/local.rb +14 -1
- data/lib/cash/lock.rb +10 -12
- data/lib/cash/query/abstract.rb +1 -1
- data/rails/init.rb +1 -1
- data/spec/cash/accessor_spec.rb +18 -3
- data/spec/cash/active_record_spec.rb +18 -17
- data/spec/cash/finders_spec.rb +10 -1
- data/spec/cash/lock_spec.rb +44 -23
- data/spec/cash/order_spec.rb +6 -0
- data/spec/cash/transactional_spec.rb +7 -7
- data/spec/spec_helper.rb +4 -3
- metadata +3 -3
- data/lib/cash/mock.rb +0 -154
|
File without changes
|
data/lib/cache_money.rb
CHANGED
|
@@ -14,7 +14,7 @@ require 'cash/config'
|
|
|
14
14
|
require 'cash/accessor'
|
|
15
15
|
|
|
16
16
|
require 'cash/request'
|
|
17
|
-
require 'cash/
|
|
17
|
+
require 'cash/fake'
|
|
18
18
|
require 'cash/local'
|
|
19
19
|
|
|
20
20
|
require 'cash/query/abstract'
|
|
@@ -28,8 +28,8 @@ require 'cash/util/marshal'
|
|
|
28
28
|
class ActiveRecord::Base
|
|
29
29
|
def self.is_cached(options = {})
|
|
30
30
|
options.assert_valid_keys(:ttl, :repository, :version)
|
|
31
|
-
include Cash
|
|
32
|
-
|
|
31
|
+
include Cash unless ancestors.include?(Cash)
|
|
32
|
+
Cash::Config::Config.create(self, options)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
data/lib/cash/accessor.rb
CHANGED
|
@@ -11,8 +11,10 @@ module Cash
|
|
|
11
11
|
def fetch(keys, options = {}, &block)
|
|
12
12
|
case keys
|
|
13
13
|
when Array
|
|
14
|
+
return {} if keys.empty?
|
|
15
|
+
|
|
14
16
|
keys = keys.collect { |key| cache_key(key) }
|
|
15
|
-
hits = repository.get_multi(keys)
|
|
17
|
+
hits = repository.get_multi(*keys)
|
|
16
18
|
if (missed_keys = keys - hits.keys).any?
|
|
17
19
|
missed_values = block.call(missed_keys)
|
|
18
20
|
hits.merge!(missed_keys.zip(Array(missed_values)).to_hash_without_nils)
|
data/lib/cash/buffered.rb
CHANGED
data/lib/cash/config.rb
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
module Cash
|
|
2
2
|
module Config
|
|
3
|
+
def self.create(active_record, options, indices = [])
|
|
4
|
+
active_record.cache_config = Config.new(active_record, options)
|
|
5
|
+
indices.each { |i| active_record.index i.attributes, i.options }
|
|
6
|
+
end
|
|
7
|
+
|
|
3
8
|
def self.included(a_module)
|
|
4
9
|
a_module.module_eval do
|
|
5
10
|
extend ClassMethods
|
|
@@ -38,11 +43,6 @@ module Cash
|
|
|
38
43
|
class Config
|
|
39
44
|
attr_reader :active_record, :options
|
|
40
45
|
|
|
41
|
-
def self.create(active_record, options, indices = [])
|
|
42
|
-
active_record.cache_config = new(active_record, options)
|
|
43
|
-
indices.each { |i| active_record.index i.attributes, i.options }
|
|
44
|
-
end
|
|
45
|
-
|
|
46
46
|
def initialize(active_record, options = {})
|
|
47
47
|
@active_record, @options = active_record, options
|
|
48
48
|
end
|
|
@@ -64,7 +64,7 @@ module Cash
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def inherit(active_record)
|
|
67
|
-
|
|
67
|
+
Cash::Config.create(active_record, @options, indices)
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
end
|
data/lib/cash/fake.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Cash
|
|
2
|
+
class Fake < HashWithIndifferentAccess
|
|
3
|
+
attr_accessor :servers
|
|
4
|
+
|
|
5
|
+
def get_multi(*keys)
|
|
6
|
+
slice(*keys).collect { |k,v| [k, Marshal.load(v)] }.to_hash
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def set(key, value, ttl = 0, raw = false)
|
|
10
|
+
self[key] = marshal(value, raw)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(key, raw = false)
|
|
14
|
+
if raw
|
|
15
|
+
self[key]
|
|
16
|
+
else
|
|
17
|
+
if self.has_key?(key)
|
|
18
|
+
Marshal.load(self[key])
|
|
19
|
+
else
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def incr(key, amount = 1)
|
|
26
|
+
if self.has_key?(key)
|
|
27
|
+
self[key] = (self[key].to_i + amount).to_s
|
|
28
|
+
self[key].to_i
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def decr(key, amount = 1)
|
|
33
|
+
if self.has_key?(key)
|
|
34
|
+
self[key] = (self[key].to_i - amount).to_s
|
|
35
|
+
self[key].to_i
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add(key, value, ttl = 0, raw = false)
|
|
40
|
+
return false if self.has_key?(key)
|
|
41
|
+
|
|
42
|
+
self[key] = marshal(value, raw)
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def append(key, value)
|
|
47
|
+
set(key, get(key, true).to_s + value.to_s, nil, true)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def namespace
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def flush_all
|
|
55
|
+
clear
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def stats
|
|
59
|
+
{}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def reset_runtime
|
|
63
|
+
[0, Hash.new(0)]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
def marshal(value, raw)
|
|
68
|
+
if raw
|
|
69
|
+
value.to_s
|
|
70
|
+
else
|
|
71
|
+
Marshal.dump(value)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def unmarshal(marshaled_obj)
|
|
76
|
+
Marshal.load(marshaled_obj)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def deep_clone(obj)
|
|
80
|
+
unmarshal(marshal(obj))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/cash/index.rb
CHANGED
|
@@ -22,15 +22,13 @@ module Cash
|
|
|
22
22
|
|
|
23
23
|
module Commands
|
|
24
24
|
def add(object)
|
|
25
|
-
clone = object.shallow_clone
|
|
26
25
|
_, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
|
|
27
|
-
add_to_index_with_minimal_network_operations(new_attribute_value_pairs,
|
|
26
|
+
add_to_index_with_minimal_network_operations(new_attribute_value_pairs, object)
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
def update(object)
|
|
31
|
-
clone = object.shallow_clone
|
|
32
30
|
old_attribute_value_pairs, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
|
|
33
|
-
update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs,
|
|
31
|
+
update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, object)
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
def remove(object)
|
|
@@ -70,7 +68,7 @@ module Cash
|
|
|
70
68
|
include Attributes
|
|
71
69
|
|
|
72
70
|
def serialize_object(object)
|
|
73
|
-
primary_key? ? object : object.id
|
|
71
|
+
primary_key? ? object.shallow_clone : object.id
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
def matches?(query)
|
|
@@ -105,7 +103,7 @@ module Cash
|
|
|
105
103
|
end
|
|
106
104
|
|
|
107
105
|
def add_object_to_primary_key_cache(attribute_value_pairs, object)
|
|
108
|
-
set(cache_key(attribute_value_pairs), [object], :ttl => ttl)
|
|
106
|
+
set(cache_key(attribute_value_pairs), [serialize_object(object)], :ttl => ttl)
|
|
109
107
|
end
|
|
110
108
|
|
|
111
109
|
def cache_key(attribute_value_pairs)
|
data/lib/cash/local.rb
CHANGED
|
@@ -14,7 +14,20 @@ module Cash
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def method_missing(method, *args, &block)
|
|
17
|
-
|
|
17
|
+
autoload_missing_constants do
|
|
18
|
+
@remote_cache.send(method, *args, &block)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def autoload_missing_constants
|
|
23
|
+
yield if block_given?
|
|
24
|
+
rescue ArgumentError, MemCache::MemCacheError => error
|
|
25
|
+
lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }
|
|
26
|
+
if error.to_s[/undefined class|referred/] && !lazy_load[error.to_s.split.last.constantize]
|
|
27
|
+
retry
|
|
28
|
+
else
|
|
29
|
+
raise error
|
|
30
|
+
end
|
|
18
31
|
end
|
|
19
32
|
end
|
|
20
33
|
|
data/lib/cash/lock.rb
CHANGED
|
@@ -2,19 +2,19 @@ module Cash
|
|
|
2
2
|
class Lock
|
|
3
3
|
class Error < RuntimeError; end
|
|
4
4
|
|
|
5
|
+
INITIAL_WAIT = 1
|
|
5
6
|
DEFAULT_RETRY = 5
|
|
6
7
|
DEFAULT_EXPIRY = 30
|
|
7
8
|
|
|
8
9
|
def initialize(cache)
|
|
9
10
|
@cache = cache
|
|
10
|
-
@runtime = Benchmark::Tms.new
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def synchronize(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY)
|
|
13
|
+
def synchronize(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY, initial_wait = INITIAL_WAIT)
|
|
14
14
|
if recursive_lock?(key)
|
|
15
15
|
yield
|
|
16
16
|
else
|
|
17
|
-
acquire_lock(key, lock_expiry, retries)
|
|
17
|
+
acquire_lock(key, lock_expiry, retries, initial_wait)
|
|
18
18
|
begin
|
|
19
19
|
yield
|
|
20
20
|
ensure
|
|
@@ -23,14 +23,12 @@ module Cash
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def acquire_lock(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY)
|
|
26
|
+
def acquire_lock(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY, initial_wait = INITIAL_WAIT)
|
|
27
27
|
retries.times do |count|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
exponential_sleep(count) unless count == retries - 1
|
|
28
|
+
response = @cache.add("lock/#{key}", Process.pid, lock_expiry)
|
|
29
|
+
return if response == "STORED\r\n"
|
|
30
|
+
raise Error if count == retries - 1
|
|
31
|
+
exponential_sleep(count, initial_wait) unless count == retries - 1
|
|
34
32
|
end
|
|
35
33
|
raise Error, "Couldn't acquire memcache lock for: #{key}"
|
|
36
34
|
end
|
|
@@ -39,8 +37,8 @@ module Cash
|
|
|
39
37
|
@cache.delete("lock/#{key}")
|
|
40
38
|
end
|
|
41
39
|
|
|
42
|
-
def exponential_sleep(count)
|
|
43
|
-
|
|
40
|
+
def exponential_sleep(count, initial_wait)
|
|
41
|
+
sleep((2**count) / initial_wait)
|
|
44
42
|
end
|
|
45
43
|
|
|
46
44
|
private
|
data/lib/cash/query/abstract.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Cash
|
|
|
33
33
|
def order
|
|
34
34
|
@order ||= begin
|
|
35
35
|
if order_sql = @options1[:order] || @options2[:order]
|
|
36
|
-
matched, table_name, column_name, direction = *(ORDER.match(order_sql))
|
|
36
|
+
matched, table_name, column_name, direction = *(ORDER.match(order_sql.to_s))
|
|
37
37
|
[column_name, direction =~ DESC ? :desc : :asc]
|
|
38
38
|
else
|
|
39
39
|
['id', :asc]
|
data/rails/init.rb
CHANGED
|
@@ -10,9 +10,9 @@ if memcache_config.nil? || memcache_config[:cache_money].nil?
|
|
|
10
10
|
else
|
|
11
11
|
require 'cache_money'
|
|
12
12
|
|
|
13
|
-
##$memcache = Rails.cache
|
|
14
13
|
memcache_config[:logger] = Rails.logger
|
|
15
14
|
$memcache = MemCache.new(memcache_config[:servers], memcache_config)
|
|
15
|
+
# $memcache = Memcached::Rails.new(memcache_config['servers'], memcache_config)
|
|
16
16
|
|
|
17
17
|
ActionController::Base.cache_store = :cache_money_mem_cache_store
|
|
18
18
|
ActionController::Base.session_options[:cache] = $memcache if memcache_options[:sessions]
|
data/spec/cash/accessor_spec.rb
CHANGED
|
@@ -19,6 +19,12 @@ module Cash
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
describe '#fetch([...])', :shared => true do
|
|
22
|
+
describe '#fetch([])' do
|
|
23
|
+
it 'returns the empty hash' do
|
|
24
|
+
Story.fetch([]).should == {}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
22
28
|
describe 'when there is a total cache miss' do
|
|
23
29
|
it 'yields the keys to the block' do
|
|
24
30
|
Story.fetch(["yabba", "dabba"]) { |*missing_ids| ["doo", "doo"] }.should == {
|
|
@@ -104,9 +110,18 @@ module Cash
|
|
|
104
110
|
|
|
105
111
|
describe '#add' do
|
|
106
112
|
describe 'when the value already exists' do
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
110
125
|
end
|
|
111
126
|
end
|
|
112
127
|
|
|
@@ -109,27 +109,28 @@ module Cash
|
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
describe '#find(:first,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
describe '#find(:first, ...)' do
|
|
113
|
+
describe '#find(:first, ..., :offset => ...)' do
|
|
114
|
+
it "#finds the object in the correct order" do
|
|
115
|
+
story1 = Story.create!(:title => 'title1')
|
|
116
|
+
story2 = Story.create!(:title => story1.title)
|
|
117
|
+
Story.find(:first, :conditions => { :title => story1.title }, :offset => 1).should == story2
|
|
118
|
+
end
|
|
117
119
|
end
|
|
118
|
-
end
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
describe '#find(:first, :conditions => [])' do
|
|
122
|
+
it 'finds the object in the correct order' do
|
|
123
|
+
story = Story.create!
|
|
124
|
+
Story.find(:first, :conditions => []).should == story
|
|
125
|
+
end
|
|
124
126
|
end
|
|
125
|
-
end
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
describe "#find(:first, :conditions => '...')" do
|
|
129
|
+
it "coerces ruby values to the appropriate database values" do
|
|
130
|
+
story1 = Story.create! :title => 'a story', :published => true
|
|
131
|
+
story2 = Story.create! :title => 'another story', :published => false
|
|
132
|
+
Story.find(:first, :conditions => 'published = 0').should == story2
|
|
133
|
+
end
|
|
133
134
|
end
|
|
134
135
|
end
|
|
135
136
|
end
|
data/spec/cash/finders_spec.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Cash
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
describe '#find(:first, ...)' do
|
|
25
|
-
describe '#find(:first, :conditions => { :id => ?})' do
|
|
25
|
+
describe '#find(:first, :conditions => { :id => ? })' do
|
|
26
26
|
it "does not use the database" do
|
|
27
27
|
story = Story.create!
|
|
28
28
|
mock(Story.connection).execute.never
|
|
@@ -99,6 +99,15 @@ module Cash
|
|
|
99
99
|
.should == story
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
|
+
|
|
103
|
+
describe 'when the attributes must be coerced to sql values' do
|
|
104
|
+
it 'does not use the database' do
|
|
105
|
+
story1 = Story.create!(:published => true)
|
|
106
|
+
story2 = Story.create!(:published => false)
|
|
107
|
+
mock(Story.connection).execute.never
|
|
108
|
+
Story.find(:first, :conditions => 'published = 0').should == story2
|
|
109
|
+
end
|
|
110
|
+
end
|
|
102
111
|
end
|
|
103
112
|
|
|
104
113
|
describe '#find(:first, :conditions => [...])' do
|
data/spec/cash/lock_spec.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Cash
|
|
|
31
31
|
$memcache.get("lock/lock_key").should == nil
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
it "does not block on recursive lock acquisition" do
|
|
35
35
|
$lock.synchronize('lock_key') do
|
|
36
36
|
lambda { $lock.synchronize('lock_key') {} }.should_not raise_error
|
|
37
37
|
end
|
|
@@ -39,43 +39,64 @@ module Cash
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
describe '#acquire_lock' do
|
|
42
|
-
|
|
42
|
+
it "creates a lock at a given cache key" do
|
|
43
43
|
$memcache.get("lock/lock_key").should == nil
|
|
44
44
|
$lock.acquire_lock("lock_key")
|
|
45
45
|
$memcache.get("lock/lock_key").should_not == nil
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
stub($lock).exponential_sleep
|
|
53
|
-
lambda { $lock.acquire_lock('lock_key', timeout, 3) }.should raise_error
|
|
48
|
+
describe 'when given a timeout for the lock' do
|
|
49
|
+
it "correctly sets timeout on memcache entries" do
|
|
50
|
+
mock($memcache).add('lock/lock_key', Process.pid, timeout = 10) { true }
|
|
51
|
+
$lock.acquire_lock('lock_key', timeout)
|
|
54
52
|
end
|
|
55
53
|
end
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
describe 'when to processes contend for a lock' do
|
|
56
|
+
it "prevents two processes from acquiring the same lock at the same time" do
|
|
57
|
+
$lock.acquire_lock('lock_key')
|
|
58
|
+
as_another_process do
|
|
59
|
+
stub($lock).exponential_sleep
|
|
60
|
+
lambda { $lock.acquire_lock('lock_key') }.should raise_error
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe 'when given a number of times to retry' do
|
|
65
|
+
it "retries specified number of times" do
|
|
66
|
+
$lock.acquire_lock('lock_key')
|
|
67
|
+
as_another_process do
|
|
68
|
+
mock($memcache).add("lock/lock_key", Process.pid, timeout = 10) { false }.times(retries = 3)
|
|
69
|
+
stub($lock).exponential_sleep
|
|
70
|
+
lambda { $lock.acquire_lock('lock_key', timeout, retries) }.should raise_error
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe 'when given an initial wait' do
|
|
76
|
+
it 'sleeps exponentially starting with the initial wait' do
|
|
77
|
+
mock($lock).sleep(initial_wait = 0.123)
|
|
78
|
+
mock($lock).sleep(2 * initial_wait)
|
|
79
|
+
mock($lock).sleep(4 * initial_wait)
|
|
80
|
+
mock($lock).sleep(8 * initial_wait)
|
|
81
|
+
$lock.acquire_lock('lock_key')
|
|
82
|
+
as_another_process do
|
|
83
|
+
lambda { $lock.acquire_lock('lock_key', Lock::DEFAULT_EXPIRY, Lock::DEFAULT_RETRY, initial_wait) }.should raise_error
|
|
84
|
+
end
|
|
85
|
+
end
|
|
66
86
|
end
|
|
67
|
-
end
|
|
68
87
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
def as_another_process
|
|
89
|
+
current_pid = Process.pid
|
|
90
|
+
stub(Process).pid { current_pid + 1 }
|
|
91
|
+
yield
|
|
92
|
+
end
|
|
93
|
+
|
|
73
94
|
end
|
|
74
95
|
|
|
75
96
|
end
|
|
76
97
|
|
|
77
98
|
describe '#release_lock' do
|
|
78
|
-
|
|
99
|
+
it "deletes the lock for a given cache key" do
|
|
79
100
|
$memcache.get("lock/lock_key").should == nil
|
|
80
101
|
$lock.acquire_lock("lock_key")
|
|
81
102
|
$memcache.get("lock/lock_key").should_not == nil
|
data/spec/cash/order_spec.rb
CHANGED
|
@@ -87,6 +87,12 @@ module Cash
|
|
|
87
87
|
FairyTale.find(:all, :conditions => { :title => @title }, :order => '`stories`.id').should == @fairy_tales
|
|
88
88
|
FairyTale.find(:all, :conditions => { :title => @title }, :order => '`stories`.`id`').should == @fairy_tales
|
|
89
89
|
end
|
|
90
|
+
|
|
91
|
+
describe 'when the order is passed as a symbol' do
|
|
92
|
+
it 'works' do
|
|
93
|
+
FairyTale.find(:all, :conditions => { :title => @title }, :order => :id)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
90
96
|
end
|
|
91
97
|
|
|
92
98
|
describe 'when the cache is not populated' do
|
|
@@ -73,14 +73,14 @@ module Cash
|
|
|
73
73
|
it 'returns a hash' do
|
|
74
74
|
@cache.set('key1', @value)
|
|
75
75
|
@cache.set('key2', @value)
|
|
76
|
-
@cache.get_multi(
|
|
76
|
+
@cache.get_multi('key1', 'key2').should == { 'key1' => @value, 'key2' => @value }
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
describe 'when there are misses' do
|
|
81
81
|
it 'only returns results for hits' do
|
|
82
82
|
@cache.set('key1', @value)
|
|
83
|
-
@cache.get_multi(
|
|
83
|
+
@cache.get_multi('key1', 'key2').should == { 'key1' => @value }
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
end
|
|
@@ -203,7 +203,7 @@ module Cash
|
|
|
203
203
|
@cache.transaction do
|
|
204
204
|
@cache.set('key1', @value)
|
|
205
205
|
@cache.set('key2', [])
|
|
206
|
-
@cache.get_multi(
|
|
206
|
+
@cache.get_multi('key1', 'key2').should == { 'key1' => @value, 'key2' => [] }
|
|
207
207
|
end
|
|
208
208
|
end
|
|
209
209
|
end
|
|
@@ -213,7 +213,7 @@ module Cash
|
|
|
213
213
|
@cache.transaction do
|
|
214
214
|
@cache.set('key1', @value)
|
|
215
215
|
@cache.set('key2', @value)
|
|
216
|
-
@cache.get_multi(
|
|
216
|
+
@cache.get_multi('key1', 'key2').should == { 'key1' => @value, 'key2' => @value }
|
|
217
217
|
end
|
|
218
218
|
end
|
|
219
219
|
end
|
|
@@ -222,7 +222,7 @@ module Cash
|
|
|
222
222
|
it 'only returns results for hits' do
|
|
223
223
|
@cache.transaction do
|
|
224
224
|
@cache.set('key1', @value)
|
|
225
|
-
@cache.get_multi(
|
|
225
|
+
@cache.get_multi('key1', 'key2').should == { 'key1' => @value }
|
|
226
226
|
end
|
|
227
227
|
end
|
|
228
228
|
end
|
|
@@ -286,8 +286,8 @@ module Cash
|
|
|
286
286
|
@cache.transaction do
|
|
287
287
|
@cache.set('key1', @value)
|
|
288
288
|
@cache.set('key2', @value)
|
|
289
|
-
@cache.get_multi(
|
|
290
|
-
$memcache.get_multi(
|
|
289
|
+
@cache.get_multi('key1', 'key2').should == { 'key1' => @value, 'key2' => @value }
|
|
290
|
+
$memcache.get_multi('key1', 'key2').should == {}
|
|
291
291
|
end
|
|
292
292
|
end
|
|
293
293
|
|
data/spec/spec_helper.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'spec'
|
|
|
6
6
|
require 'pp'
|
|
7
7
|
require 'cache_money'
|
|
8
8
|
require 'memcache'
|
|
9
|
+
# require 'memcached'
|
|
9
10
|
require File.join(dir, '../config/environment')
|
|
10
11
|
|
|
11
12
|
Spec::Runner.configure do |config|
|
|
@@ -13,9 +14,9 @@ Spec::Runner.configure do |config|
|
|
|
13
14
|
config.before :suite do
|
|
14
15
|
load File.join(dir, "../db/schema.rb")
|
|
15
16
|
|
|
16
|
-
config = YAML.load(IO.read((File.expand_path(File.dirname(__FILE__) + "/../config/
|
|
17
|
-
$memcache = MemCache.new(config)
|
|
18
|
-
$memcache
|
|
17
|
+
config = YAML.load(IO.read((File.expand_path(File.dirname(__FILE__) + "/../config/memcached.yml"))))['test']
|
|
18
|
+
$memcache = MemCache.new(config['servers'], config)
|
|
19
|
+
# $memcache = Memcached::Rails.new(config['servers'], config)
|
|
19
20
|
$lock = Cash::Lock.new($memcache)
|
|
20
21
|
end
|
|
21
22
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ashleym1972-cache-money
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Kallen
|
|
@@ -61,7 +61,7 @@ files:
|
|
|
61
61
|
- lib/cash/index.rb
|
|
62
62
|
- lib/cash/local.rb
|
|
63
63
|
- lib/cash/lock.rb
|
|
64
|
-
- lib/cash/
|
|
64
|
+
- lib/cash/fake.rb
|
|
65
65
|
- lib/cash/query/abstract.rb
|
|
66
66
|
- lib/cash/query/calculation.rb
|
|
67
67
|
- lib/cash/query/primary_key.rb
|
|
@@ -103,7 +103,7 @@ specification_version: 2
|
|
|
103
103
|
summary: Write-through and Read-through Cacheing for ActiveRecord
|
|
104
104
|
test_files:
|
|
105
105
|
- config/environment.rb
|
|
106
|
-
- config/
|
|
106
|
+
- config/memcached.yml
|
|
107
107
|
- db/schema.rb
|
|
108
108
|
- spec/cash/accessor_spec.rb
|
|
109
109
|
- spec/cash/active_record_spec.rb
|
data/lib/cash/mock.rb
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
module Cash
|
|
2
|
-
class Mock < HashWithIndifferentAccess
|
|
3
|
-
attr_accessor :servers
|
|
4
|
-
|
|
5
|
-
class CacheEntry
|
|
6
|
-
attr_reader :value
|
|
7
|
-
|
|
8
|
-
def self.default_ttl
|
|
9
|
-
1_000_000
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def self.now
|
|
13
|
-
Time.now
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def initialize(value, raw, ttl)
|
|
17
|
-
if raw
|
|
18
|
-
@value = value.to_s
|
|
19
|
-
else
|
|
20
|
-
@value = Marshal.dump(value)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
if ttl.zero?
|
|
24
|
-
@ttl = self.class.default_ttl
|
|
25
|
-
else
|
|
26
|
-
@ttl = ttl
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
@expires_at = self.class.now + @ttl
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def expired?
|
|
34
|
-
self.class.now > @expires_at
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def increment(amount = 1)
|
|
38
|
-
@value = (@value.to_i + amount).to_s
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def decrement(amount = 1)
|
|
42
|
-
@value = (@value.to_i - amount).to_s
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def unmarshal
|
|
46
|
-
Marshal.load(@value)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def to_i
|
|
50
|
-
@value.to_i
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
attr_accessor :logging
|
|
55
|
-
|
|
56
|
-
def initialize
|
|
57
|
-
@logging = false
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def get_multi(keys)
|
|
61
|
-
slice(*keys).collect { |k,v| [k, v.unmarshal] }.to_hash_without_nils
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def set(key, value, ttl = CacheEntry.default_ttl, raw = false)
|
|
65
|
-
log "< set #{key} #{ttl}"
|
|
66
|
-
self[key] = CacheEntry.new(value, raw, ttl)
|
|
67
|
-
log('> STORED')
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def get(key, raw = false)
|
|
71
|
-
log "< get #{key}"
|
|
72
|
-
unless self.has_unexpired_key?(key)
|
|
73
|
-
log('> END')
|
|
74
|
-
return nil
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
log("> sending key #{key}")
|
|
78
|
-
log('> END')
|
|
79
|
-
if raw
|
|
80
|
-
self[key].value
|
|
81
|
-
else
|
|
82
|
-
self[key].unmarshal
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def delete(key, options = {})
|
|
87
|
-
log "< delete #{key}"
|
|
88
|
-
if self.has_unexpired_key?(key)
|
|
89
|
-
log "> DELETED"
|
|
90
|
-
super(key)
|
|
91
|
-
else
|
|
92
|
-
log "> NOT FOUND"
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def incr(key, amount = 1)
|
|
97
|
-
if self.has_unexpired_key?(key)
|
|
98
|
-
self[key].increment(amount)
|
|
99
|
-
self[key].to_i
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def decr(key, amount = 1)
|
|
104
|
-
if self.has_unexpired_key?(key)
|
|
105
|
-
self[key].decrement(amount)
|
|
106
|
-
self[key].to_i
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def add(key, value, ttl = CacheEntry.default_ttl, raw = false)
|
|
111
|
-
if self.has_unexpired_key?(key)
|
|
112
|
-
"NOT_STORED\r\n"
|
|
113
|
-
else
|
|
114
|
-
set(key, value, ttl, raw)
|
|
115
|
-
"STORED\r\n"
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def append(key, value)
|
|
120
|
-
set(key, get(key, true).to_s + value.to_s, nil, true)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def namespace
|
|
124
|
-
nil
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def flush_all
|
|
128
|
-
log('< flush_all')
|
|
129
|
-
clear
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def stats
|
|
133
|
-
{}
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def reset_runtime
|
|
137
|
-
[0, Hash.new(0)]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def has_unexpired_key?(key)
|
|
141
|
-
self.has_key?(key) && !self[key].expired?
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def log(message)
|
|
145
|
-
return unless logging
|
|
146
|
-
logger.debug(message)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def logger
|
|
150
|
-
@logger ||= ActiveSupport::BufferedLogger.new(Rails.root.join('log/cash_mock.log'))
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
end
|
|
154
|
-
end
|