seifertd-seifertd-cache-money 0.2.5.1

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.
@@ -0,0 +1,71 @@
1
+ module Cash
2
+ module Config
3
+ def self.included(a_module)
4
+ a_module.module_eval do
5
+ extend ClassMethods
6
+ delegate :repository, :to => "self.class"
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def self.extended(a_class)
12
+ class << a_class
13
+ attr_reader :cache_config
14
+ delegate :repository, :indices, :to => :@cache_config
15
+ alias_method_chain :inherited, :cache_config
16
+ end
17
+ end
18
+
19
+ def inherited_with_cache_config(subclass)
20
+ inherited_without_cache_config(subclass)
21
+ @cache_config.inherit(subclass)
22
+ end
23
+
24
+ def index(attributes, options = {})
25
+ options.assert_valid_keys(:ttl, :order, :limit, :buffer)
26
+ (@cache_config.indices.unshift(Index.new(@cache_config, self, attributes, options))).uniq!
27
+ end
28
+
29
+ def version(number)
30
+ @cache_config.options[:version] = number
31
+ end
32
+
33
+ def cache_config=(config)
34
+ @cache_config = config
35
+ end
36
+ end
37
+
38
+ class Config
39
+ attr_reader :active_record, :options
40
+
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
+ def initialize(active_record, options = {})
47
+ @active_record, @options = active_record, options
48
+ end
49
+
50
+ def repository
51
+ @options[:repository]
52
+ end
53
+
54
+ def ttl
55
+ @options[:ttl]
56
+ end
57
+
58
+ def version
59
+ @options[:version] || 1
60
+ end
61
+
62
+ def indices
63
+ @indices ||= active_record == ActiveRecord::Base ? [] : [Index.new(self, active_record, active_record.primary_key)]
64
+ end
65
+
66
+ def inherit(active_record)
67
+ self.class.create(active_record, @options, indices)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,38 @@
1
+ module Cash
2
+ module Finders
3
+ def self.included(active_record_class)
4
+ active_record_class.class_eval do
5
+ extend ClassMethods
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+ def self.extended(active_record_class)
11
+ class << active_record_class
12
+ alias_method_chain :find_every, :cache
13
+ alias_method_chain :find_from_ids, :cache
14
+ alias_method_chain :calculate, :cache
15
+ end
16
+ end
17
+
18
+ def without_cache(&block)
19
+ with_scope(:find => {:readonly => true}, &block)
20
+ end
21
+
22
+ # User.find(:first, ...), User.find_by_foo(...), User.find(:all, ...), User.find_all_by_foo(...)
23
+ def find_every_with_cache(options)
24
+ Query::Select.perform(self, options, scope(:find))
25
+ end
26
+
27
+ # User.find(1), User.find(1, 2, 3), User.find([1, 2, 3]), User.find([])
28
+ def find_from_ids_with_cache(ids, options)
29
+ Query::PrimaryKey.perform(self, ids, options, scope(:find))
30
+ end
31
+
32
+ # User.count(:all), User.count, User.sum(...)
33
+ def calculate_with_cache(operation, column_name, options = {})
34
+ Query::Calculation.perform(self, operation, column_name, options, scope(:find))
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,207 @@
1
+ module Cash
2
+ class Index
3
+ attr_reader :attributes, :options
4
+ delegate :each, :hash, :to => :@attributes
5
+ delegate :get, :set, :expire, :find_every_without_cache, :calculate_without_cache, :calculate_with_cache, :incr, :decr, :primary_key, :to => :@active_record
6
+
7
+ DEFAULT_OPTIONS = { :ttl => 1.day }
8
+
9
+ def initialize(config, active_record, attributes, options = {})
10
+ @config, @active_record, @attributes, @options = config, active_record, Array(attributes).collect(&:to_s).sort, DEFAULT_OPTIONS.merge(options)
11
+ end
12
+
13
+ def ==(other)
14
+ case other
15
+ when Index
16
+ attributes == other.attributes
17
+ else
18
+ attributes == Array(other)
19
+ end
20
+ end
21
+ alias_method :eql?, :==
22
+
23
+ module Commands
24
+ def add(object)
25
+ clone = object.shallow_clone
26
+ _, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
27
+ add_to_index_with_minimal_network_operations(new_attribute_value_pairs, clone)
28
+ end
29
+
30
+ def update(object)
31
+ clone = object.shallow_clone
32
+ 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, clone)
34
+ end
35
+
36
+ def remove(object)
37
+ old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
38
+ remove_from_index_with_minimal_network_operations(old_attribute_value_pairs, object)
39
+ end
40
+
41
+ def delete(object)
42
+ old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
43
+ key = cache_key(old_attribute_value_pairs)
44
+ expire(key)
45
+ end
46
+ end
47
+ include Commands
48
+
49
+ module Attributes
50
+ def ttl
51
+ @ttl ||= options[:ttl] || config.ttl
52
+ end
53
+
54
+ def order
55
+ @order ||= options[:order] || :asc
56
+ end
57
+
58
+ def limit
59
+ options[:limit]
60
+ end
61
+
62
+ def buffer
63
+ options[:buffer]
64
+ end
65
+
66
+ def window
67
+ limit && limit + buffer
68
+ end
69
+ end
70
+ include Attributes
71
+
72
+ def serialize_object(object)
73
+ primary_key? ? object : object.id
74
+ end
75
+
76
+ def matches?(query)
77
+ query.calculation? ||
78
+ (query.order == ['id', order] &&
79
+ (!limit || (query.limit && query.limit + query.offset <= limit)))
80
+ end
81
+
82
+ private
83
+ def old_and_new_attribute_value_pairs(object)
84
+ old_attribute_value_pairs = []
85
+ new_attribute_value_pairs = []
86
+ @attributes.each do |name|
87
+ new_value = object.attributes[name]
88
+ original_value = object.send("#{name}_was")
89
+ old_attribute_value_pairs << [name, original_value]
90
+ new_attribute_value_pairs << [name, new_value]
91
+ end
92
+ [old_attribute_value_pairs, new_attribute_value_pairs]
93
+ end
94
+
95
+ def add_to_index_with_minimal_network_operations(attribute_value_pairs, object)
96
+ if primary_key?
97
+ add_object_to_primary_key_cache(attribute_value_pairs, object)
98
+ else
99
+ add_object_to_cache(attribute_value_pairs, object)
100
+ end
101
+ end
102
+
103
+ def primary_key?
104
+ @attributes.size == 1 && @attributes.first == primary_key
105
+ end
106
+
107
+ def add_object_to_primary_key_cache(attribute_value_pairs, object)
108
+ set(cache_key(attribute_value_pairs), [object], :ttl => ttl)
109
+ end
110
+
111
+ def cache_key(attribute_value_pairs)
112
+ attribute_value_pairs.flatten.join('/')
113
+ end
114
+
115
+ def add_object_to_cache(attribute_value_pairs, object, overwrite = true)
116
+ return if invalid_cache_key?(attribute_value_pairs)
117
+
118
+ key, cache_value, cache_hit = get_key_and_value_at_index(attribute_value_pairs)
119
+ if !cache_hit || overwrite
120
+ object_to_add = serialize_object(object)
121
+ objects = (cache_value + [object_to_add]).sort do |a, b|
122
+ (a <=> b) * (order == :asc ? 1 : -1)
123
+ end.uniq
124
+ objects = truncate_if_necessary(objects)
125
+ set(key, objects, :ttl => ttl)
126
+ incr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
127
+ end
128
+ end
129
+
130
+ def invalid_cache_key?(attribute_value_pairs)
131
+ attribute_value_pairs.collect { |_,value| value }.any? { |x| x.nil? }
132
+ end
133
+
134
+ def get_key_and_value_at_index(attribute_value_pairs)
135
+ key = cache_key(attribute_value_pairs)
136
+ cache_hit = true
137
+ cache_value = get(key) do
138
+ cache_hit = false
139
+ conditions = attribute_value_pairs.to_hash
140
+ find_every_without_cache(:select => primary_key, :conditions => conditions, :limit => window).collect do |object|
141
+ serialize_object(object)
142
+ end
143
+ end
144
+ [key, cache_value, cache_hit]
145
+ end
146
+
147
+ def truncate_if_necessary(objects)
148
+ objects.slice(0, window || objects.size)
149
+ end
150
+
151
+ def calculate_at_index(operation, attribute_value_pairs)
152
+ conditions = attribute_value_pairs.to_hash
153
+ calculate_without_cache(operation, :all, :conditions => conditions)
154
+ end
155
+
156
+ def update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, object)
157
+ if index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
158
+ remove_object_from_cache(old_attribute_value_pairs, object)
159
+ add_object_to_cache(new_attribute_value_pairs, object)
160
+ elsif primary_key?
161
+ add_object_to_primary_key_cache(new_attribute_value_pairs, object)
162
+ else
163
+ add_object_to_cache(new_attribute_value_pairs, object, false)
164
+ end
165
+ end
166
+
167
+ def index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
168
+ old_attribute_value_pairs != new_attribute_value_pairs
169
+ end
170
+
171
+ def remove_from_index_with_minimal_network_operations(attribute_value_pairs, object)
172
+ if primary_key?
173
+ remove_object_from_primary_key_cache(attribute_value_pairs, object)
174
+ else
175
+ remove_object_from_cache(attribute_value_pairs, object)
176
+ end
177
+ end
178
+
179
+ def remove_object_from_primary_key_cache(attribute_value_pairs, object)
180
+ set(cache_key(attribute_value_pairs), [], :ttl => ttl)
181
+ end
182
+
183
+ def remove_object_from_cache(attribute_value_pairs, object)
184
+ return if invalid_cache_key?(attribute_value_pairs)
185
+
186
+ key, cache_value, _ = get_key_and_value_at_index(attribute_value_pairs)
187
+ object_to_remove = serialize_object(object)
188
+ objects = cache_value - [object_to_remove]
189
+ objects = resize_if_necessary(attribute_value_pairs, objects)
190
+ set(key, objects, :ttl => ttl)
191
+ end
192
+
193
+ def resize_if_necessary(attribute_value_pairs, objects)
194
+ conditions = attribute_value_pairs.to_hash
195
+ key = cache_key(attribute_value_pairs)
196
+ count = decr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
197
+
198
+ if limit && objects.size < limit && objects.size < count
199
+ find_every_without_cache(:select => :id, :conditions => conditions).collect do |object|
200
+ serialize_object(object)
201
+ end
202
+ else
203
+ objects
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,59 @@
1
+ module Cash
2
+ class Local
3
+ delegate :respond_to?, :to => :@remote_cache
4
+
5
+ def initialize(remote_cache)
6
+ @remote_cache = remote_cache
7
+ end
8
+
9
+ def cache_locally
10
+ @remote_cache = LocalBuffer.new(original_cache = @remote_cache)
11
+ yield
12
+ ensure
13
+ @remote_cache = original_cache
14
+ end
15
+
16
+ def method_missing(method, *args, &block)
17
+ @remote_cache.send(method, *args, &block)
18
+ end
19
+ end
20
+
21
+ class LocalBuffer
22
+ delegate :respond_to?, :to => :@remote_cache
23
+
24
+ def initialize(remote_cache)
25
+ @local_cache = {}
26
+ @remote_cache = remote_cache
27
+ end
28
+
29
+ def get(key, *options)
30
+ if @local_cache.has_key?(key)
31
+ @local_cache[key]
32
+ else
33
+ @local_cache[key] = @remote_cache.get(key, *options)
34
+ end
35
+ end
36
+
37
+ def set(key, value, *options)
38
+ @remote_cache.set(key, value, *options)
39
+ @local_cache[key] = value
40
+ end
41
+
42
+ def add(key, value, *options)
43
+ result = @remote_cache.add(key, value, *options)
44
+ if result == "STORED\r\n"
45
+ @local_cache[key] = value
46
+ end
47
+ result
48
+ end
49
+
50
+ def delete(key, *options)
51
+ @remote_cache.delete(key, *options)
52
+ @local_cache.delete(key)
53
+ end
54
+
55
+ def method_missing(method, *args, &block)
56
+ @remote_cache.send(method, *args, &block)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,53 @@
1
+ module Cash
2
+ class Lock
3
+ class Error < RuntimeError; end
4
+
5
+ DEFAULT_RETRY = 5
6
+ DEFAULT_EXPIRY = 30
7
+
8
+ def initialize(cache)
9
+ @cache = cache
10
+ @runtime = Benchmark::Tms.new
11
+ end
12
+
13
+ def synchronize(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY)
14
+ if recursive_lock?(key)
15
+ yield
16
+ else
17
+ acquire_lock(key, lock_expiry, retries)
18
+ begin
19
+ yield
20
+ ensure
21
+ release_lock(key)
22
+ end
23
+ end
24
+ end
25
+
26
+ def acquire_lock(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY)
27
+ retries.times do |count|
28
+ begin
29
+ response = @cache.add("lock/#{key}", Process.pid, lock_expiry)
30
+ return if response == "STORED\r\n"
31
+ raise Error if count == retries - 1
32
+ end
33
+ exponential_sleep(count) unless count == retries - 1
34
+ end
35
+ raise Error, "Couldn't acquire memcache lock for: #{key}"
36
+ end
37
+
38
+ def release_lock(key)
39
+ @cache.delete("lock/#{key}")
40
+ end
41
+
42
+ def exponential_sleep(count)
43
+ @runtime += Benchmark::measure { sleep((2**count) / 2.0) }
44
+ end
45
+
46
+ private
47
+
48
+ def recursive_lock?(key)
49
+ @cache.get("lock/#{key}") == Process.pid
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,86 @@
1
+ module Cash
2
+ class Mock < 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
+ if self.has_key?(key)
41
+ "NOT_STORED\r\n"
42
+ else
43
+ self[key] = marshal(value, raw)
44
+ "STORED\r\n"
45
+ end
46
+ end
47
+
48
+ def append(key, value)
49
+ set(key, get(key, true).to_s + value.to_s, nil, true)
50
+ end
51
+
52
+ def namespace
53
+ nil
54
+ end
55
+
56
+ def flush_all
57
+ clear
58
+ end
59
+
60
+ def stats
61
+ {}
62
+ end
63
+
64
+ def reset_runtime
65
+ [0, Hash.new(0)]
66
+ end
67
+
68
+ private
69
+
70
+ def marshal(value, raw)
71
+ if raw
72
+ value.to_s
73
+ else
74
+ Marshal.dump(value)
75
+ end
76
+ end
77
+
78
+ def unmarshal(marshaled_obj)
79
+ Marshal.load(marshaled_obj)
80
+ end
81
+
82
+ def deep_clone(obj)
83
+ unmarshal(marshal(obj))
84
+ end
85
+ end
86
+ end