mguymon-cache-money 0.2.12

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