af-cache-money 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,71 @@
1
+ module Cash
2
+ module Config
3
+ def self.create(active_record, options, indices = [])
4
+ active_record.cache_config = Cash::Config::Config.new(active_record, options)
5
+ indices.each { |i| active_record.index i.attributes, i.options }
6
+ end
7
+
8
+ def self.included(a_module)
9
+ a_module.module_eval do
10
+ extend ClassMethods
11
+ delegate :repository, :to => "self.class"
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def self.extended(a_class)
17
+ class << a_class
18
+ attr_reader :cache_config
19
+ delegate :repository, :indices, :to => :@cache_config
20
+ alias_method_chain :inherited, :cache_config
21
+ end
22
+ end
23
+
24
+ def inherited_with_cache_config(subclass)
25
+ inherited_without_cache_config(subclass)
26
+ @cache_config.inherit(subclass)
27
+ end
28
+
29
+ def index(attributes, options = {})
30
+ options.assert_valid_keys(:ttl, :order, :limit, :buffer, :order_column)
31
+ (@cache_config.indices.unshift(Index.new(@cache_config, self, attributes, options))).uniq!
32
+ end
33
+
34
+ def version(number)
35
+ @cache_config.options[:version] = number
36
+ end
37
+
38
+ def cache_config=(config)
39
+ @cache_config = config
40
+ end
41
+ end
42
+
43
+ class Config
44
+ attr_reader :active_record, :options
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] || 0
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
+ Cash::Config.create(active_record, @options, indices)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -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
@@ -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,209 @@
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, :logger, :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
+ _, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
26
+ add_to_index_with_minimal_network_operations(new_attribute_value_pairs, object)
27
+ end
28
+
29
+ def update(object)
30
+ old_attribute_value_pairs, new_attribute_value_pairs = old_and_new_attribute_value_pairs(object)
31
+ update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, object)
32
+ end
33
+
34
+ def remove(object)
35
+ old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
36
+ remove_from_index_with_minimal_network_operations(old_attribute_value_pairs, object)
37
+ end
38
+
39
+ def delete(object)
40
+ old_attribute_value_pairs, _ = old_and_new_attribute_value_pairs(object)
41
+ key = cache_key(old_attribute_value_pairs)
42
+ expire(key)
43
+ end
44
+ end
45
+ include Commands
46
+
47
+ module Attributes
48
+ def ttl
49
+ @ttl ||= options[:ttl] || config.ttl
50
+ end
51
+
52
+ def order
53
+ @order ||= options[:order] || :asc
54
+ end
55
+
56
+ def limit
57
+ options[:limit]
58
+ end
59
+
60
+ def buffer
61
+ options[:buffer]
62
+ end
63
+
64
+ def window
65
+ limit && limit + buffer
66
+ end
67
+
68
+ def order_column
69
+ options[:order_column] || 'id'
70
+ end
71
+ end
72
+ include Attributes
73
+
74
+ def serialize_object(object)
75
+ primary_key? ? object.shallow_clone : object.id
76
+ end
77
+
78
+ def matches?(query)
79
+ query.calculation? ||
80
+ (query.order == [order_column, order] &&
81
+ (!limit || (query.limit && query.limit + query.offset <= limit)))
82
+ end
83
+
84
+ private
85
+ def old_and_new_attribute_value_pairs(object)
86
+ old_attribute_value_pairs = []
87
+ new_attribute_value_pairs = []
88
+ @attributes.each do |name|
89
+ new_value = object.attributes[name]
90
+ original_value = object.send("#{name}_was")
91
+ old_attribute_value_pairs << [name, original_value]
92
+ new_attribute_value_pairs << [name, new_value]
93
+ end
94
+ [old_attribute_value_pairs, new_attribute_value_pairs]
95
+ end
96
+
97
+ def add_to_index_with_minimal_network_operations(attribute_value_pairs, object)
98
+ if primary_key?
99
+ add_object_to_primary_key_cache(attribute_value_pairs, object)
100
+ else
101
+ add_object_to_cache(attribute_value_pairs, object)
102
+ end
103
+ end
104
+
105
+ def primary_key?
106
+ @attributes.size == 1 && @attributes.first == primary_key
107
+ end
108
+
109
+ def add_object_to_primary_key_cache(attribute_value_pairs, object)
110
+ set(cache_key(attribute_value_pairs), [serialize_object(object)], :ttl => ttl)
111
+ end
112
+
113
+ def cache_key(attribute_value_pairs)
114
+ attribute_value_pairs.flatten.join('/')
115
+ end
116
+
117
+ def add_object_to_cache(attribute_value_pairs, object, overwrite = true)
118
+ return if invalid_cache_key?(attribute_value_pairs)
119
+
120
+ key, cache_value, cache_hit = get_key_and_value_at_index(attribute_value_pairs)
121
+ if !cache_hit || overwrite
122
+ object_to_add = serialize_object(object)
123
+ objects = (cache_value + [object_to_add]).sort do |a, b|
124
+ (a <=> b) * (order == :asc ? 1 : -1)
125
+ end.uniq
126
+ objects = truncate_if_necessary(objects)
127
+ set(key, objects, :ttl => ttl)
128
+ incr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
129
+ end
130
+ end
131
+
132
+ def invalid_cache_key?(attribute_value_pairs)
133
+ attribute_value_pairs.collect { |_,value| value }.any? { |x| x.nil? }
134
+ end
135
+
136
+ def get_key_and_value_at_index(attribute_value_pairs)
137
+ key = cache_key(attribute_value_pairs)
138
+ cache_hit = true
139
+ cache_value = get(key) do
140
+ cache_hit = false
141
+ conditions = attribute_value_pairs.to_hash_without_nils
142
+ find_every_without_cache(:conditions => conditions, :limit => window).collect do |object|
143
+ serialize_object(object)
144
+ end
145
+ end
146
+ [key, cache_value, cache_hit]
147
+ end
148
+
149
+ def truncate_if_necessary(objects)
150
+ objects.slice(0, window || objects.size)
151
+ end
152
+
153
+ def calculate_at_index(operation, attribute_value_pairs)
154
+ conditions = attribute_value_pairs.to_hash_without_nils
155
+ calculate_without_cache(operation, :all, :conditions => conditions)
156
+ end
157
+
158
+ def update_index_with_minimal_network_operations(old_attribute_value_pairs, new_attribute_value_pairs, object)
159
+ if index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
160
+ remove_object_from_cache(old_attribute_value_pairs, object)
161
+ add_object_to_cache(new_attribute_value_pairs, object)
162
+ elsif primary_key?
163
+ add_object_to_primary_key_cache(new_attribute_value_pairs, object)
164
+ else
165
+ add_object_to_cache(new_attribute_value_pairs, object, false)
166
+ end
167
+ end
168
+
169
+ def index_is_stale?(old_attribute_value_pairs, new_attribute_value_pairs)
170
+ old_attribute_value_pairs != new_attribute_value_pairs
171
+ end
172
+
173
+ def remove_from_index_with_minimal_network_operations(attribute_value_pairs, object)
174
+ if primary_key?
175
+ remove_object_from_primary_key_cache(attribute_value_pairs, object)
176
+ else
177
+ remove_object_from_cache(attribute_value_pairs, object)
178
+ end
179
+ end
180
+
181
+ def remove_object_from_primary_key_cache(attribute_value_pairs, object)
182
+ set(cache_key(attribute_value_pairs), [], :ttl => ttl)
183
+ end
184
+
185
+ def remove_object_from_cache(attribute_value_pairs, object)
186
+ return if invalid_cache_key?(attribute_value_pairs)
187
+
188
+ key, cache_value, _ = get_key_and_value_at_index(attribute_value_pairs)
189
+ object_to_remove = serialize_object(object)
190
+ objects = cache_value - [object_to_remove]
191
+ objects = resize_if_necessary(attribute_value_pairs, objects)
192
+ set(key, objects, :ttl => ttl)
193
+ end
194
+
195
+ def resize_if_necessary(attribute_value_pairs, objects)
196
+ conditions = attribute_value_pairs.to_hash_without_nils
197
+ key = cache_key(attribute_value_pairs)
198
+ count = decr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
199
+
200
+ if limit && objects.size < limit && objects.size < count
201
+ find_every_without_cache(:select => :id, :conditions => conditions).collect do |object|
202
+ serialize_object(object)
203
+ end
204
+ else
205
+ objects
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,72 @@
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 if block_given?
12
+ ensure
13
+ @remote_cache = original_cache
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
+
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
31
+ end
32
+ end
33
+
34
+ class LocalBuffer
35
+ delegate :respond_to?, :to => :@remote_cache
36
+
37
+ def initialize(remote_cache)
38
+ @local_cache = {}
39
+ @remote_cache = remote_cache
40
+ end
41
+
42
+ def get(key, *options)
43
+ if @local_cache.has_key?(key)
44
+ @local_cache[key]
45
+ else
46
+ @local_cache[key] = @remote_cache.get(key, *options)
47
+ end
48
+ end
49
+
50
+ def set(key, value, *options)
51
+ @remote_cache.set(key, value, *options)
52
+ @local_cache[key] = value
53
+ end
54
+
55
+ def add(key, value, *options)
56
+ result = @remote_cache.add(key, value, *options)
57
+ if result == "STORED\r\n"
58
+ @local_cache[key] = value
59
+ end
60
+ result
61
+ end
62
+
63
+ def delete(key, *options)
64
+ @remote_cache.delete(key, *options)
65
+ @local_cache.delete(key)
66
+ end
67
+
68
+ def method_missing(method, *args, &block)
69
+ @remote_cache.send(method, *args, &block)
70
+ end
71
+ end
72
+ end