ngmoco-cache-money 0.2.9

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.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