mguymon-cache-money 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,76 @@
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 autoload_missing_constants
17
+ yield if block_given?
18
+ rescue ArgumentError, MemCache::MemCacheError => error
19
+ lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }
20
+ if error.to_s[/undefined class|referred/] && !lazy_load[error.to_s.split.last.constantize]
21
+ retry
22
+ else
23
+ raise error
24
+ end
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
34
+ end
35
+
36
+ class LocalBuffer
37
+ delegate :respond_to?, :to => :@remote_cache
38
+
39
+ def initialize(remote_cache)
40
+ @local_cache = {}
41
+ @remote_cache = remote_cache
42
+ end
43
+
44
+ def get(key, *options)
45
+ if @local_cache.has_key?(key)
46
+ @local_cache[key]
47
+ else
48
+ @local_cache[key] = @remote_cache.get(key, *options)
49
+ end
50
+ end
51
+
52
+ def set(key, value, *options)
53
+ @remote_cache.set(key, value, *options)
54
+ @local_cache[key] = value
55
+ end
56
+
57
+ def add(key, value, *options)
58
+ result = @remote_cache.add(key, value, *options)
59
+ if result == "STORED\r\n"
60
+ @local_cache[key] = value
61
+ end
62
+ result
63
+ end
64
+
65
+ def delete(key, *options)
66
+ @remote_cache.delete(key, *options)
67
+ @local_cache.delete(key)
68
+ end
69
+
70
+ private
71
+
72
+ def method_missing(method, *args, &block)
73
+ @remote_cache.send(method, *args, &block)
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,63 @@
1
+ require 'socket'
2
+
3
+ module Cash
4
+ class Lock
5
+ class Error < RuntimeError; end
6
+
7
+ INITIAL_WAIT = 2
8
+ DEFAULT_RETRY = 8
9
+ DEFAULT_EXPIRY = 30
10
+
11
+ def initialize(cache)
12
+ @cache = cache
13
+ end
14
+
15
+ def synchronize(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY, initial_wait = INITIAL_WAIT)
16
+ if recursive_lock?(key)
17
+ yield
18
+ else
19
+ acquire_lock(key, lock_expiry, retries, initial_wait)
20
+ begin
21
+ yield
22
+ ensure
23
+ release_lock(key)
24
+ end
25
+ end
26
+ end
27
+
28
+ def acquire_lock(key, lock_expiry = DEFAULT_EXPIRY, retries = DEFAULT_RETRY, initial_wait = INITIAL_WAIT)
29
+ retries.times do |count|
30
+ response = @cache.add("lock/#{key}", host_pid, lock_expiry)
31
+ return if response == "STORED\r\n"
32
+ return if recursive_lock?(key)
33
+ exponential_sleep(count, initial_wait) unless count == retries - 1
34
+ end
35
+ debug_lock(key)
36
+ raise Error, "Couldn't acquire memcache lock on #{@cache.get_server_for_key("lock/#{key}")}"
37
+ end
38
+
39
+ def release_lock(key)
40
+ @cache.delete("lock/#{key}")
41
+ end
42
+
43
+ def exponential_sleep(count, initial_wait)
44
+ sleep((2**count) / initial_wait)
45
+ end
46
+
47
+ private
48
+
49
+ def recursive_lock?(key)
50
+ @cache.get("lock/#{key}") == host_pid
51
+ end
52
+
53
+ def debug_lock(key)
54
+ @cache.logger.warn("Cash::Lock[#{key}]: #{@cache.get("lock/#{key}")}") if @cache.respond_to?(:logger) && @cache.logger.respond_to?(:warn)
55
+ rescue
56
+ @cache.logger.warn("#{$!}") if @cache.respond_to?(:logger) && @cache.logger.respond_to?(:warn)
57
+ end
58
+
59
+ def host_pid
60
+ "#{Socket.gethostname} #{Process.pid}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,194 @@
1
+ module Cash
2
+ module Query
3
+ class Abstract
4
+ delegate :with_exclusive_scope, :get, :table_name, :indices, :find_from_ids_without_cache, :cache_key, :columns_hash, :logger, :to => :@active_record
5
+
6
+ def self.perform(*args)
7
+ new(*args).perform
8
+ end
9
+
10
+ def initialize(active_record, options1, options2)
11
+ @active_record, @options1, @options2 = active_record, options1, options2 || {}
12
+
13
+ # if @options2.empty? and active_record.base_class != active_record
14
+ # @options2 = { :conditions => { active_record.inheritance_column => active_record.to_s }}
15
+ # end
16
+ # if active_record.base_class != active_record
17
+ # @options2[:conditions] = active_record.merge_conditions(
18
+ # @options2[:conditions], { active_record.inheritance_column => active_record.to_s }
19
+ # )
20
+ # end
21
+ end
22
+
23
+ def perform(find_options = {}, get_options = {})
24
+ if cache_config = cacheable?(@options1, @options2, find_options)
25
+ cache_keys, index = cache_keys(cache_config[0]), cache_config[1]
26
+
27
+ misses, missed_keys, objects = hit_or_miss(cache_keys, index, get_options)
28
+ format_results(cache_keys, choose_deserialized_objects_if_possible(missed_keys, cache_keys, misses, objects))
29
+ else
30
+ logger.debug(" \e[1;4;31mUNCACHEABLE\e[0m #{table_name} - #{find_options.inspect} - #{get_options.inspect} - #{@options1.inspect} - #{@options2.inspect}") if logger
31
+ uncacheable
32
+ end
33
+ end
34
+
35
+ DESC = /DESC/i
36
+
37
+ def order
38
+ @order ||= begin
39
+ if order_sql = @options1[:order] || @options2[:order]
40
+ matched, table_name, column_name, direction = *(ORDER.match(order_sql.to_s))
41
+ [column_name, direction =~ DESC ? :desc : :asc]
42
+ else
43
+ ['id', :asc]
44
+ end
45
+ end
46
+ rescue TypeError
47
+ ['id', :asc]
48
+ end
49
+
50
+ def limit
51
+ @limit ||= @options1[:limit] || @options2[:limit]
52
+ end
53
+
54
+ def offset
55
+ @offset ||= @options1[:offset] || @options2[:offset] || 0
56
+ end
57
+
58
+ def calculation?
59
+ false
60
+ end
61
+
62
+ private
63
+ def cacheable?(*optionss)
64
+ return false if @active_record.respond_to?(:cachable?) && ! @active_record.cachable?(*optionss)
65
+ optionss.each { |options| return unless safe_options_for_cache?(options) }
66
+ partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
67
+ return if partial_indices.include?(nil)
68
+ attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
69
+
70
+ # attribute_value_pairs.each do |attribute_value_pair|
71
+ # return false if attribute_value_pair.last.is_a?(Array)
72
+ # end
73
+
74
+ if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
75
+ if index.matches?(self)
76
+ [attribute_value_pairs, index]
77
+ end
78
+ end
79
+ end
80
+
81
+ def hit_or_miss(cache_keys, index, options)
82
+ misses, missed_keys = nil, nil
83
+ objects = @active_record.get(cache_keys, options.merge(:ttl => index.ttl)) do |missed_keys|
84
+ misses = miss(missed_keys, @options1.merge(:limit => index.window))
85
+ serialize_objects(index, misses)
86
+ end
87
+ [misses, missed_keys, objects]
88
+ end
89
+
90
+ def cache_keys(attribute_value_pairs)
91
+ attribute_value_pairs.flatten.join('/')
92
+ end
93
+
94
+ def safe_options_for_cache?(options)
95
+ return false unless options.kind_of?(Hash)
96
+ options.except(:conditions, :readonly, :limit, :offset, :order).values.compact.empty? && !options[:readonly]
97
+ end
98
+
99
+ def attribute_value_pairs_for_conditions(conditions)
100
+ case conditions
101
+ when Hash
102
+ conditions.to_a.collect { |key, value| [key.to_s, value] }
103
+ when String
104
+ parse_indices_from_condition(conditions.gsub('1 = 1 AND ', '')) #ignore unnecessary conditions
105
+ when Array
106
+ parse_indices_from_condition(*conditions)
107
+ when NilClass
108
+ []
109
+ end
110
+ end
111
+
112
+ AND = /\s+AND\s+/i
113
+ TABLE_AND_COLUMN = /(?:(?:`|")?(\w+)(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
114
+ VALUE = /'?(\d+|\?|(?:(?:[^']|'')*))'?/ # Matches: 123, ?, '123', '12''3'
115
+ KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s+=\s+#{VALUE}\)?$/ # Matches: KEY = VALUE, (KEY = VALUE)
116
+ ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i # Matches: COLUMN ASC, COLUMN DESC, COLUMN
117
+
118
+ def parse_indices_from_condition(conditions = '', *values)
119
+ values = values.dup
120
+ conditions.split(AND).inject([]) do |indices, condition|
121
+ matched, table_name, column_name, sql_value = *(KEY_EQ_VALUE.match(condition))
122
+ if matched
123
+ # value = sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
124
+ if sql_value == '?'
125
+ value = values.shift
126
+ else
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
135
+ end
136
+ indices << [column_name, value]
137
+ else
138
+ return nil
139
+ end
140
+ end
141
+ end
142
+
143
+ def indexed_on?(attributes)
144
+ indices.detect { |index| index == attributes }
145
+ rescue NoMethodError
146
+ nil
147
+ end
148
+ alias_method :index_for, :indexed_on?
149
+
150
+ def format_results(cache_keys, objects)
151
+ return objects if objects.blank?
152
+
153
+ objects = convert_to_array(cache_keys, objects)
154
+ objects = apply_limits_and_offsets(objects, @options1)
155
+ deserialize_objects(objects)
156
+ end
157
+
158
+ def choose_deserialized_objects_if_possible(missed_keys, cache_keys, misses, objects)
159
+ missed_keys == cache_keys ? misses : objects
160
+ end
161
+
162
+ def serialize_objects(index, objects)
163
+ Array(objects).collect { |missed| index.serialize_object(missed) }
164
+ end
165
+
166
+ def convert_to_array(cache_keys, object)
167
+ if object.kind_of?(Hash)
168
+ cache_keys.collect { |key| object[cache_key(key)] }.flatten.compact
169
+ else
170
+ Array(object)
171
+ end
172
+ end
173
+
174
+ def apply_limits_and_offsets(results, options)
175
+ results.slice((options[:offset] || 0), (options[:limit] || results.length))
176
+ end
177
+
178
+ def deserialize_objects(objects)
179
+ if objects.first.kind_of?(ActiveRecord::Base)
180
+ objects
181
+ else
182
+ cache_keys = objects.collect { |id| "id/#{id}" }
183
+ objects = get(cache_keys, &method(:find_from_keys))
184
+ convert_to_array(cache_keys, objects)
185
+ end
186
+ end
187
+
188
+ def find_from_keys(*missing_keys)
189
+ missing_ids = Array(missing_keys).flatten.collect { |key| key.split('/')[2].to_i }
190
+ find_from_ids_without_cache(missing_ids, {})
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,45 @@
1
+ module Cash
2
+ module Query
3
+ class Calculation < Abstract
4
+ delegate :calculate_without_cache, :incr, :to => :@active_record
5
+
6
+ def initialize(active_record, operation, column, options1, options2)
7
+ super(active_record, options1, options2)
8
+ @operation, @column = operation, column
9
+ end
10
+
11
+ def perform
12
+ super({}, :raw => true)
13
+ end
14
+
15
+ def calculation?
16
+ true
17
+ end
18
+
19
+ protected
20
+ def miss(_, __)
21
+ calculate_without_cache(@operation, @column, @options1)
22
+ end
23
+
24
+ def uncacheable
25
+ calculate_without_cache(@operation, @column, @options1)
26
+ end
27
+
28
+ def format_results(_, objects)
29
+ objects.to_i
30
+ end
31
+
32
+ def serialize_objects(_, objects)
33
+ objects.to_s
34
+ end
35
+
36
+ def cacheable?(*optionss)
37
+ @column == :all && super(*optionss)
38
+ end
39
+
40
+ def cache_keys(attribute_value_pairs)
41
+ "#{super(attribute_value_pairs)}/#{@operation}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ module Cash
2
+ module Query
3
+ class PrimaryKey < Abstract
4
+ def initialize(active_record, ids, options1, options2)
5
+ super(active_record, options1, options2)
6
+ @expects_array = ids.first.kind_of?(Array)
7
+ @original_ids = ids
8
+ @ids = ids.flatten.compact.uniq.collect do |object|
9
+ object.respond_to?(:quoted_id) ? object.quoted_id : object.to_i
10
+ end
11
+ end
12
+
13
+ def perform
14
+ return [] if @expects_array && @ids.empty?
15
+ raise ActiveRecord::RecordNotFound if @ids.empty?
16
+
17
+ super(:conditions => { :id => @ids.first })
18
+ end
19
+
20
+ protected
21
+ def deserialize_objects(objects)
22
+ convert_to_active_record_collection(super(objects))
23
+ end
24
+
25
+ def cache_keys(attribute_value_pairs)
26
+ @ids.collect { |id| "id/#{id}" }
27
+ end
28
+
29
+ def miss(missing_keys, options)
30
+ find_from_keys(*missing_keys)
31
+ end
32
+
33
+ def uncacheable
34
+ find_from_ids_without_cache(@original_ids, @options1)
35
+ end
36
+
37
+ private
38
+ def convert_to_active_record_collection(objects)
39
+ case objects.size
40
+ when 0
41
+ raise ActiveRecord::RecordNotFound
42
+ when 1
43
+ @expects_array ? objects : objects.first
44
+ else
45
+ objects
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,16 @@
1
+ module Cash
2
+ module Query
3
+ class Select < Abstract
4
+ delegate :find_every_without_cache, :to => :@active_record
5
+
6
+ protected
7
+ def miss(_, miss_options)
8
+ find_every_without_cache(miss_options)
9
+ end
10
+
11
+ def uncacheable
12
+ find_every_without_cache(@options1)
13
+ end
14
+ end
15
+ end
16
+ end