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/db/schema.rb
CHANGED
data/lib/cache_money.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'rubygems'
|
4
|
-
require 'activesupport'
|
5
|
-
require 'activerecord'
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_record'
|
6
3
|
|
7
4
|
require 'cash/lock'
|
8
5
|
require 'cash/transactional'
|
@@ -27,9 +24,21 @@ require 'cash/util/marshal'
|
|
27
24
|
|
28
25
|
class ActiveRecord::Base
|
29
26
|
def self.is_cached(options = {})
|
30
|
-
options
|
31
|
-
|
32
|
-
|
27
|
+
if options == false
|
28
|
+
include NoCash
|
29
|
+
else
|
30
|
+
options.assert_valid_keys(:ttl, :repository, :version)
|
31
|
+
include Cash unless ancestors.include?(Cash)
|
32
|
+
Cash::Config.create(self, options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def <=>(other)
|
37
|
+
if self.id == other.id then
|
38
|
+
0
|
39
|
+
else
|
40
|
+
self.id < other.id ? -1 : 1
|
41
|
+
end
|
33
42
|
end
|
34
43
|
end
|
35
44
|
|
@@ -49,7 +58,27 @@ module Cash
|
|
49
58
|
end
|
50
59
|
|
51
60
|
def transaction_with_cache_transaction(&block)
|
52
|
-
|
61
|
+
if cache_config
|
62
|
+
repository.transaction { transaction_without_cache_transaction(&block) }
|
63
|
+
else
|
64
|
+
transaction_without_cache_transaction(&block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def cacheable?(*args)
|
69
|
+
true
|
53
70
|
end
|
54
71
|
end
|
55
72
|
end
|
73
|
+
module NoCash
|
74
|
+
def self.included(active_record_class)
|
75
|
+
active_record_class.class_eval do
|
76
|
+
extend ClassMethods
|
77
|
+
end
|
78
|
+
end
|
79
|
+
module ClassMethods
|
80
|
+
def cachable?(*args)
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/cash/buffered.rb
CHANGED
@@ -80,15 +80,12 @@ module Cash
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
-
def method_missing(method, *args, &block)
|
84
|
-
@cache.send(method, *args, &block)
|
85
|
-
end
|
86
|
-
|
87
83
|
def respond_to?(method)
|
88
84
|
@cache.respond_to?(method)
|
89
85
|
end
|
90
86
|
|
91
87
|
protected
|
88
|
+
|
92
89
|
def perform_commands
|
93
90
|
@commands.each do |command|
|
94
91
|
command.call(@cache)
|
@@ -98,6 +95,12 @@ module Cash
|
|
98
95
|
def buffer_command(command)
|
99
96
|
@commands << command
|
100
97
|
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def method_missing(method, *args, &block)
|
102
|
+
@cache.send(method, *args, &block)
|
103
|
+
end
|
101
104
|
end
|
102
105
|
|
103
106
|
class NestedBuffered < Buffered
|
data/lib/cash/config.rb
CHANGED
@@ -15,8 +15,11 @@ module Cash
|
|
15
15
|
module ClassMethods
|
16
16
|
def self.extended(a_class)
|
17
17
|
class << a_class
|
18
|
-
|
19
|
-
|
18
|
+
def cache_config
|
19
|
+
@cache_config ? @cache_config : superclass.cache_config
|
20
|
+
end
|
21
|
+
|
22
|
+
delegate :repository, :indices, :to => :cache_config
|
20
23
|
alias_method_chain :inherited, :cache_config
|
21
24
|
end
|
22
25
|
end
|
@@ -52,7 +55,7 @@ module Cash
|
|
52
55
|
end
|
53
56
|
|
54
57
|
def ttl
|
55
|
-
@options[:ttl] ||
|
58
|
+
@ttl ||= @options[:ttl] || repository.default_ttl || 1.day
|
56
59
|
end
|
57
60
|
|
58
61
|
def version
|
data/lib/cash/index.rb
CHANGED
@@ -7,6 +7,7 @@ module Cash
|
|
7
7
|
DEFAULT_OPTIONS = { :ttl => 1.day }
|
8
8
|
|
9
9
|
def initialize(config, active_record, attributes, options = {})
|
10
|
+
DEFAULT_OPTIONS[:ttl] = config.ttl || DEFAULT_OPTIONS[:ttl]
|
10
11
|
@config, @active_record, @attributes, @options = config, active_record, Array(attributes).collect(&:to_s).sort, DEFAULT_OPTIONS.merge(options)
|
11
12
|
end
|
12
13
|
|
@@ -87,7 +88,11 @@ module Cash
|
|
87
88
|
new_attribute_value_pairs = []
|
88
89
|
@attributes.each do |name|
|
89
90
|
new_value = object.attributes[name]
|
90
|
-
|
91
|
+
if object.changed.include? name
|
92
|
+
original_value = object.send("#{name}_was")
|
93
|
+
else
|
94
|
+
original_value = new_value
|
95
|
+
end
|
91
96
|
old_attribute_value_pairs << [name, original_value]
|
92
97
|
new_attribute_value_pairs << [name, new_value]
|
93
98
|
end
|
data/lib/cash/local.rb
CHANGED
@@ -12,12 +12,6 @@ module Cash
|
|
12
12
|
ensure
|
13
13
|
@remote_cache = original_cache
|
14
14
|
end
|
15
|
-
|
16
|
-
def method_missing(method, *args, &block)
|
17
|
-
autoload_missing_constants do
|
18
|
-
@remote_cache.send(method, *args, &block)
|
19
|
-
end
|
20
|
-
end
|
21
15
|
|
22
16
|
def autoload_missing_constants
|
23
17
|
yield if block_given?
|
@@ -29,6 +23,14 @@ module Cash
|
|
29
23
|
raise error
|
30
24
|
end
|
31
25
|
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def method_missing(method, *args, &block)
|
30
|
+
autoload_missing_constants do
|
31
|
+
@remote_cache.send(method, *args, &block)
|
32
|
+
end
|
33
|
+
end
|
32
34
|
end
|
33
35
|
|
34
36
|
class LocalBuffer
|
@@ -65,6 +67,8 @@ module Cash
|
|
65
67
|
@local_cache.delete(key)
|
66
68
|
end
|
67
69
|
|
70
|
+
private
|
71
|
+
|
68
72
|
def method_missing(method, *args, &block)
|
69
73
|
@remote_cache.send(method, *args, &block)
|
70
74
|
end
|
data/lib/cash/lock.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
1
3
|
module Cash
|
2
4
|
class Lock
|
3
5
|
class Error < RuntimeError; end
|
@@ -25,12 +27,13 @@ module Cash
|
|
25
27
|
|
26
28
|
def acquire_lock(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY, initial_wait = INITIAL_WAIT)
|
27
29
|
retries.times do |count|
|
28
|
-
response = @cache.add("lock/#{key}",
|
30
|
+
response = @cache.add("lock/#{key}", host_pid, lock_expiry)
|
29
31
|
return if response == "STORED\r\n"
|
32
|
+
return if recursive_lock?(key)
|
30
33
|
exponential_sleep(count, initial_wait) unless count == retries - 1
|
31
34
|
end
|
32
35
|
debug_lock(key)
|
33
|
-
raise Error, "Couldn't acquire memcache lock
|
36
|
+
raise Error, "Couldn't acquire memcache lock on #{@cache.get_server_for_key("lock/#{key}")}"
|
34
37
|
end
|
35
38
|
|
36
39
|
def release_lock(key)
|
@@ -44,13 +47,17 @@ module Cash
|
|
44
47
|
private
|
45
48
|
|
46
49
|
def recursive_lock?(key)
|
47
|
-
@cache.get("lock/#{key}") ==
|
50
|
+
@cache.get("lock/#{key}") == host_pid
|
48
51
|
end
|
49
52
|
|
50
53
|
def debug_lock(key)
|
51
|
-
@cache.logger.warn("#{@cache.get("lock/#{key}")}") if @cache.respond_to?(:logger) && @cache.logger.respond_to?(:warn)
|
54
|
+
@cache.logger.warn("Cash::Lock[#{key}]: #{@cache.get("lock/#{key}")}") if @cache.respond_to?(:logger) && @cache.logger.respond_to?(:warn)
|
52
55
|
rescue
|
53
56
|
@cache.logger.warn("#{$!}") if @cache.respond_to?(:logger) && @cache.logger.respond_to?(:warn)
|
54
57
|
end
|
58
|
+
|
59
|
+
def host_pid
|
60
|
+
"#{Socket.gethostname} #{Process.pid}"
|
61
|
+
end
|
55
62
|
end
|
56
63
|
end
|
data/lib/cash/mock.rb
ADDED
@@ -0,0 +1,154 @@
|
|
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
|
data/lib/cash/query/abstract.rb
CHANGED
@@ -27,7 +27,7 @@ module Cash
|
|
27
27
|
misses, missed_keys, objects = hit_or_miss(cache_keys, index, get_options)
|
28
28
|
format_results(cache_keys, choose_deserialized_objects_if_possible(missed_keys, cache_keys, misses, objects))
|
29
29
|
else
|
30
|
-
logger.debug("
|
30
|
+
logger.debug(" \e[1;4;31mUNCACHEABLE\e[0m #{table_name} - #{find_options.inspect} - #{get_options.inspect} - #{@options1.inspect} - #{@options2.inspect}") if logger
|
31
31
|
uncacheable
|
32
32
|
end
|
33
33
|
end
|
@@ -61,6 +61,7 @@ module Cash
|
|
61
61
|
|
62
62
|
private
|
63
63
|
def cacheable?(*optionss)
|
64
|
+
return false if @active_record.respond_to?(:cachable?) && ! @active_record.cachable?(*optionss)
|
64
65
|
optionss.each { |options| return unless safe_options_for_cache?(options) }
|
65
66
|
partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
|
66
67
|
return if partial_indices.include?(nil)
|
@@ -122,11 +123,15 @@ module Cash
|
|
122
123
|
# value = sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
|
123
124
|
if sql_value == '?'
|
124
125
|
value = values.shift
|
125
|
-
elsif sql_value[0..0] == ':' && values && values.count > 0 && values[0].is_a?(Hash)
|
126
|
-
symb = sql_value[1..-1].to_sym
|
127
|
-
value = columns_hash[column_name].type_cast(values[0][symb])
|
128
126
|
else
|
129
|
-
|
127
|
+
column = columns_hash[column_name]
|
128
|
+
raise "could not find column #{column_name} in columns #{columns_hash.keys.join(',')}" if column.nil?
|
129
|
+
if sql_value[0..0] == ':' && values && values.count > 0 && values[0].is_a?(Hash)
|
130
|
+
symb = sql_value[1..-1].to_sym
|
131
|
+
value = column.type_cast(values[0][symb])
|
132
|
+
else
|
133
|
+
value = column.type_cast(sql_value)
|
134
|
+
end
|
130
135
|
end
|
131
136
|
indices << [column_name, value]
|
132
137
|
else
|
@@ -182,7 +187,10 @@ module Cash
|
|
182
187
|
|
183
188
|
def find_from_keys(*missing_keys)
|
184
189
|
missing_ids = Array(missing_keys).flatten.collect { |key| key.split('/')[2].to_i }
|
185
|
-
|
190
|
+
options = @options1.dup
|
191
|
+
options.delete(:conditions)
|
192
|
+
options.delete(:limit)
|
193
|
+
find_from_ids_without_cache(missing_ids, options)
|
186
194
|
end
|
187
195
|
end
|
188
196
|
end
|
data/lib/cash/transactional.rb
CHANGED
@@ -22,15 +22,16 @@ module Cash
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def method_missing(method, *args, &block)
|
26
|
-
@cache.send(method, *args, &block)
|
27
|
-
end
|
28
|
-
|
29
25
|
def respond_to?(method)
|
30
26
|
@cache.respond_to?(method)
|
31
27
|
end
|
32
28
|
|
33
29
|
private
|
30
|
+
|
31
|
+
def method_missing(method, *args, &block)
|
32
|
+
@cache.send(method, *args, &block)
|
33
|
+
end
|
34
|
+
|
34
35
|
def begin_transaction
|
35
36
|
@cache = Buffered.push(@cache, @lock)
|
36
37
|
end
|