ngmoco-cache-money 0.2.23 → 0.2.24.2

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,152 @@
1
+ # Maps Redis methods and semantics to those of memcache-client
2
+ module Cash
3
+ module Adapter
4
+ class Redis
5
+ def initialize(repository, options = {})
6
+ @repository = repository
7
+ @logger = options[:logger]
8
+ @default_ttl = options[:default_ttl] || raise(":default_ttl is a required option")
9
+ end
10
+
11
+ def add(key, value, ttl=nil, raw = false)
12
+ wrap(key, not_stored) do
13
+ logger.debug("Redis add: #{key.inspect}") if debug_logger?
14
+ value = dump(value) unless raw
15
+ # TODO: make transactional
16
+ result = @repository.setnx(key, value)
17
+ @repository.expires(key, ttl || @default_ttl) if 1 == result
18
+ logger.debug("Redis hit: #{key.inspect}") if debug_logger?
19
+ 1 == result ? stored : not_stored
20
+ end
21
+ end
22
+
23
+ def get(key, raw = false)
24
+ wrap(key) do
25
+ logger.debug("Redis get: #{key.inspect}") if debug_logger?
26
+ value = wrap(key) { @repository.get(key) }
27
+ if value
28
+ logger.debug("Redis hit: #{key.inspect}") if debug_logger?
29
+ value = load(value) unless raw
30
+ else
31
+ logger.debug("Redis miss: #{key.inspect}") if debug_logger?
32
+ end
33
+ value
34
+ end
35
+ end
36
+
37
+ def get_multi(*keys)
38
+ wrap(keys, {}) do
39
+ keys.flatten!
40
+ logger.debug("Redis get_multi: #{keys.inspect}") if debug_logger?
41
+
42
+ # Values are returned as an array. Convert them to a hash of matches, dropping anything
43
+ # that doesn't have a match.
44
+ values = @repository.mget(*keys)
45
+ result = {}
46
+ keys.each_with_index{ |key, i| result[key] = load(values[i]) if values[i] }
47
+
48
+ if result.any?
49
+ logger.debug("Redis hit: #{keys.inspect}") if debug_logger?
50
+ else
51
+ logger.debug("Redis miss: #{keys.inspect}") if debug_logger?
52
+ end
53
+ result
54
+ end
55
+ end
56
+
57
+ def set(key, value, ttl=nil, raw = false)
58
+ wrap(key, not_stored) do
59
+ logger.debug("Redis set: #{key.inspect}") if debug_logger?
60
+ value = dump(value) unless raw
61
+ @repository.setex(key, ttl || @default_ttl, value)
62
+ logger.debug("Redis hit: #{key.inspect}") if debug_logger?
63
+ stored
64
+ end
65
+ end
66
+
67
+ def delete(key)
68
+ wrap(key, not_found) do
69
+ logger.debug("Redis delete: #{key.inspect}") if debug_logger?
70
+ @repository.del(key)
71
+ logger.debug("Redis hit: #{key.inspect}") if debug_logger?
72
+ deleted
73
+ end
74
+ end
75
+
76
+ def get_server_for_key(key)
77
+ wrap(key) do
78
+ # Redis::Distributed has a node_for method.
79
+ client = @repository.respond_to?(:node_for) ? @repository.node_for(key) : @repository.client
80
+ client.id
81
+ end
82
+ end
83
+
84
+ def incr(key, value = 1)
85
+ # Redis always answeres positively to incr/decr but memcache does not and waits for the key
86
+ # to be added in a separate operation.
87
+ if wrap(nil) { @repository.exists(key) }
88
+ wrap(key) { @repository.incrby(key, value).to_i }
89
+ end
90
+ end
91
+
92
+ def decr(key, value = 1)
93
+ if wrap(nil) { @repository.exists(key) }
94
+ wrap(key) { @repository.decrby(key, value).to_i }
95
+ end
96
+ end
97
+
98
+ def flush_all
99
+ @repository.flushall
100
+ end
101
+
102
+ def exception_classes
103
+ [Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL]
104
+ end
105
+
106
+ private
107
+
108
+ def logger
109
+ @logger
110
+ end
111
+
112
+ def debug_logger?
113
+ logger && logger.respond_to?(:debug?) && logger.debug?
114
+ end
115
+
116
+ def wrap(key, error_value = nil)
117
+ yield
118
+ rescue *exception_classes
119
+ log_error($!) if logger
120
+ error_value
121
+ end
122
+
123
+ def dump(value)
124
+ Marshal.dump(value)
125
+ end
126
+
127
+ def load(value)
128
+ Marshal.load(value)
129
+ end
130
+
131
+ def stored
132
+ "STORED\r\n"
133
+ end
134
+
135
+ def deleted
136
+ "DELETED\r\n"
137
+ end
138
+
139
+ def not_stored
140
+ "NOT_STORED\r\n"
141
+ end
142
+
143
+ def not_found
144
+ "NOT_FOUND\r\n"
145
+ end
146
+
147
+ def log_error(err)
148
+ logger.error("Redis ERROR, #{err.class}: #{err}") if logger
149
+ end
150
+ end
151
+ end
152
+ end
@@ -16,7 +16,7 @@ module Cash
16
16
  def self.extended(a_class)
17
17
  class << a_class
18
18
  def cache_config
19
- @cache_config ? @cache_config : superclass.cache_config
19
+ @cache_config
20
20
  end
21
21
 
22
22
  delegate :repository, :indices, :to => :cache_config
@@ -26,7 +26,7 @@ module Cash
26
26
 
27
27
  def inherited_with_cache_config(subclass)
28
28
  inherited_without_cache_config(subclass)
29
- @cache_config.inherit(subclass)
29
+ @cache_config.inherit(subclass) if @cache_config
30
30
  end
31
31
 
32
32
  def index(attributes, options = {})
@@ -41,6 +41,10 @@ module Cash
41
41
  def cache_config=(config)
42
42
  @cache_config = config
43
43
  end
44
+
45
+ def cacheable?(*args)
46
+ Cash.enabled && cache_config
47
+ end
44
48
  end
45
49
 
46
50
  class Config
@@ -55,8 +59,7 @@ module Cash
55
59
  end
56
60
 
57
61
  def ttl
58
- repository_ttl = repository.respond_to?(:default_ttl) ? repository.default_ttl : nil
59
- @ttl ||= @options[:ttl] || repository_ttl || 1.day
62
+ @ttl ||= (repository.respond_to?(:default_ttl) && repository.default_ttl) || @options[:ttl]
60
63
  end
61
64
 
62
65
  def version
@@ -21,17 +21,29 @@ module Cash
21
21
 
22
22
  # User.find(:first, ...), User.find_by_foo(...), User.find(:all, ...), User.find_all_by_foo(...)
23
23
  def find_every_with_cache(options)
24
- Query::Select.perform(self, options, scope(:find))
24
+ if cacheable?
25
+ Query::Select.perform(self, options, scope(:find))
26
+ else
27
+ find_every_without_cache(options)
28
+ end
25
29
  end
26
30
 
27
31
  # User.find(1), User.find(1, 2, 3), User.find([1, 2, 3]), User.find([])
28
32
  def find_from_ids_with_cache(ids, options)
29
- Query::PrimaryKey.perform(self, ids, options, scope(:find))
33
+ if cacheable?
34
+ Query::PrimaryKey.perform(self, ids, options, scope(:find))
35
+ else
36
+ find_from_ids_without_cache(ids, options)
37
+ end
30
38
  end
31
39
 
32
40
  # User.count(:all), User.count, User.sum(...)
33
41
  def calculate_with_cache(operation, column_name, options = {})
34
- Query::Calculation.perform(self, operation, column_name, options, scope(:find))
42
+ if cacheable?
43
+ Query::Calculation.perform(self, operation, column_name, options, scope(:find))
44
+ else
45
+ calculate_without_cache(operation, column_name, options)
46
+ end
35
47
  end
36
48
  end
37
49
  end
@@ -4,11 +4,8 @@ module Cash
4
4
  delegate :each, :hash, :to => :@attributes
5
5
  delegate :get, :set, :expire, :find_every_without_cache, :calculate_without_cache, :calculate_with_cache, :incr, :decr, :primary_key, :logger, :to => :@active_record
6
6
 
7
- DEFAULT_OPTIONS = { :ttl => 1.day }
8
-
9
7
  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)
8
+ @config, @active_record, @attributes, @options = config, active_record, Array(attributes).collect(&:to_s).sort, options
12
9
  end
13
10
 
14
11
  def ==(other)
@@ -47,7 +44,7 @@ module Cash
47
44
 
48
45
  module Attributes
49
46
  def ttl
50
- @ttl ||= options[:ttl] || config.ttl
47
+ @ttl ||= options[:ttl] || @config.ttl
51
48
  end
52
49
 
53
50
  def order
@@ -129,7 +126,7 @@ module Cash
129
126
  (a <=> b) * (order == :asc ? 1 : -1)
130
127
  end.uniq
131
128
  objects = truncate_if_necessary(objects)
132
- set(key, objects, :ttl => ttl)
129
+ set(key, objects, :ttl => ttl)
133
130
  incr("#{key}/count") { calculate_at_index(:count, attribute_value_pairs) }
134
131
  end
135
132
  end
@@ -15,7 +15,7 @@ module Cash
15
15
 
16
16
  def autoload_missing_constants
17
17
  yield if block_given?
18
- rescue ArgumentError, MemCache::MemCacheError => error
18
+ rescue ArgumentError, *@remote_cache.exception_classes => error
19
19
  lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }
20
20
  if error.to_s[/undefined class|referred/] && !lazy_load[error.to_s.split.last.constantize]
21
21
  retry
@@ -33,7 +33,7 @@ module Cash
33
33
  exponential_sleep(count, initial_wait) unless count == retries - 1
34
34
  end
35
35
  debug_lock(key)
36
- raise Error, "Couldn't acquire memcache lock on #{@cache.get_server_for_key("lock/#{key}")}"
36
+ raise Error, "Couldn't acquire memcache lock on 'lock/#{key}'"
37
37
  end
38
38
 
39
39
  def release_lock(key)
@@ -41,7 +41,7 @@ module Cash
41
41
  end
42
42
 
43
43
  def exponential_sleep(count, initial_wait)
44
- sleep((2**count) / initial_wait)
44
+ sleep((2**count) * initial_wait)
45
45
  end
46
46
 
47
47
  private
@@ -20,7 +20,7 @@ module Cash
20
20
  @value = Marshal.dump(value)
21
21
  end
22
22
 
23
- if ttl.zero?
23
+ if ttl.nil? || ttl.zero?
24
24
  @ttl = self.class.default_ttl
25
25
  else
26
26
  @ttl = ttl
@@ -68,18 +68,22 @@ module Cash
68
68
  end
69
69
 
70
70
  def get(key, raw = false)
71
- log "< get #{key}"
72
- unless self.has_unexpired_key?(key)
73
- log('> END')
74
- return nil
75
- end
76
-
77
- log("> sending key #{key}")
78
- log('> END')
79
- if raw
80
- self[key].value
71
+ if key.is_a?(Array)
72
+ get_multi(*key)
81
73
  else
82
- self[key].unmarshal
74
+ log "< get #{key}"
75
+ unless self.has_unexpired_key?(key)
76
+ log('> END')
77
+ return nil
78
+ end
79
+
80
+ log("> sending key #{key}")
81
+ log('> END')
82
+ if raw
83
+ self[key].value
84
+ else
85
+ self[key].unmarshal
86
+ end
83
87
  end
84
88
  end
85
89
 
@@ -61,8 +61,22 @@ module Cash
61
61
 
62
62
  private
63
63
  def cacheable?(*optionss)
64
- return false if @active_record.respond_to?(:cacheable?) && ! @active_record.cacheable?(*optionss)
65
- optionss.each { |options| return unless safe_options_for_cache?(options) }
64
+ if @active_record.respond_to?(:cacheable?) && ! @active_record.cacheable?(*optionss)
65
+ if logger
66
+ if @active_record.respond_to?(:cacheable?)
67
+ logger.debug(" \e[1;4;31mUNCACHEABLE CLASS\e[0m #{table_name}")
68
+ else
69
+ logger.debug(" \e[1;4;31mUNCACHEABLE INSTANCE\e[0m #{table_name} - #{optionss.inspect}")
70
+ end
71
+ end
72
+ return false
73
+ end
74
+ optionss.each do |options|
75
+ unless safe_options_for_cache?(options)
76
+ logger.debug(" \e[1;4;31mUNCACHEABLE UNSAFE\e[0m #{table_name} - #{options.inspect}") if logger
77
+ return false
78
+ end
79
+ end
66
80
  partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
67
81
  return if partial_indices.include?(nil)
68
82
  attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
@@ -74,7 +88,13 @@ module Cash
74
88
  if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
75
89
  if index.matches?(self)
76
90
  [attribute_value_pairs, index]
91
+ else
92
+ logger.debug(" \e[1;4;31mUNCACHEABLE NO MATCHING INDEX\e[0m #{table_name} - #{index.order_column.inspect} #{index.order.inspect} #{index.limit.inspect}") if logger
93
+ false
77
94
  end
95
+ else
96
+ logger.debug(" \e[1;4;31mUNCACHEABLE NOT INDEXED\e[0m #{table_name} - #{attribute_value_pairs.collect { |pair| pair[0] }.inspect}") if logger
97
+ false
78
98
  end
79
99
  end
80
100
 
@@ -191,7 +211,13 @@ module Cash
191
211
  order_sql = @options1[:order] || @options2[:order]
192
212
  options[:order] = order_sql if order_sql
193
213
  results = find_from_ids_without_cache(missing_ids, options)
194
- results.each {|o| @active_record.add_to_caches(o) } if results && results.is_a?(Array)
214
+ if results
215
+ if results.is_a?(Array)
216
+ results.each {|o| @active_record.add_to_caches(o) }
217
+ else
218
+ @active_record.add_to_caches(results)
219
+ end
220
+ end
195
221
  results
196
222
  end
197
223
  end
@@ -0,0 +1,3 @@
1
+ module Cash
2
+ VERSION = '0.2.24.2'
3
+ end
@@ -1,6 +1,6 @@
1
1
  module Cash
2
2
  module WriteThrough
3
- DEFAULT_TTL = 12.hours
3
+ DEFAULT_TTL = 12.hours.to_i
4
4
 
5
5
  def self.included(active_record_class)
6
6
  active_record_class.class_eval do
@@ -50,19 +50,23 @@ module Cash
50
50
 
51
51
  module ClassMethods
52
52
  def add_to_caches(object)
53
- indices.each { |index| index.add(object) } if cache_config
53
+ indices.each { |index| index.add(object) } if cacheable?
54
+ true
54
55
  end
55
56
 
56
57
  def update_caches(object)
57
- indices.each { |index| index.update(object) } if cache_config
58
+ indices.each { |index| index.update(object) } if cacheable?
59
+ true
58
60
  end
59
61
 
60
62
  def remove_from_caches(object)
61
- indices.each { |index| index.remove(object) } if cache_config
63
+ indices.each { |index| index.remove(object) } if cacheable?
64
+ true
62
65
  end
63
66
 
64
67
  def expire_caches(object)
65
- indices.each { |index| index.delete(object) } if cache_config
68
+ indices.each { |index| index.delete(object) } if cacheable?
69
+ true
66
70
  end
67
71
  end
68
72
  end
@@ -126,7 +126,7 @@ require 'memcached'
126
126
 
127
127
  private
128
128
  def expires_in(options)
129
- (options && options[:expires_in]) || 0
129
+ (options && options[:expires_in] && options[:expires_in].to_i) || 0
130
130
  end
131
131
 
132
132
  def marshal?(options)
@@ -1,40 +1 @@
1
- yml = YAML.load(IO.read(File.join(RAILS_ROOT, "config", "memcached.yml")))
2
- memcache_config = yml[RAILS_ENV]
3
- memcache_config.symbolize_keys! if memcache_config.respond_to?(:symbolize_keys!)
4
-
5
- if defined?(DISABLE_CACHE_MONEY) || ENV['DISABLE_CACHE_MONEY'] == 'true' || memcache_config.nil? || memcache_config[:cache_money] != true
6
- Rails.logger.info 'cache-money disabled'
7
- class ActiveRecord::Base
8
- def self.index(*args)
9
- end
10
- end
11
- else
12
- Rails.logger.info 'cache-money enabled'
13
- require 'cache_money'
14
-
15
- memcache_config[:logger] = Rails.logger
16
- memcache_servers =
17
- case memcache_config[:servers].class.to_s
18
- when "String"; memcache_config[:servers].gsub(' ', '').split(',')
19
- when "Array"; memcache_config[:servers]
20
- end
21
- $memcache = MemcachedWrapper.new(memcache_servers, memcache_config)
22
-
23
- #ActionController::Base.cache_store = :cache_money_mem_cache_store
24
- ActionController::Base.session_options[:cache] = $memcache if memcache_config[:sessions]
25
- #silence_warnings {
26
- # Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(:cache_money_mem_cache_store)
27
- #}
28
-
29
- $local = Cash::Local.new($memcache)
30
- $lock = Cash::Lock.new($memcache)
31
- $cache = Cash::Transactional.new($local, $lock)
32
-
33
- # allow setting up caching on a per-model basis
34
- unless memcache_config[:automatic_caching].to_s == 'false'
35
- Rails.logger.info "cache-money: global model caching enabled"
36
- class ActiveRecord::Base
37
- is_cached(:repository => $cache)
38
- end
39
- end
40
- end
1
+ require 'cache_money'