viximo-cache-money 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +201 -0
- data/README +204 -0
- data/README.markdown +204 -0
- data/TODO +17 -0
- data/UNSUPPORTED_FEATURES +13 -0
- data/config/environment.rb +8 -0
- data/config/memcached.yml +4 -0
- data/db/schema.rb +18 -0
- data/init.rb +1 -0
- data/lib/cache_money.rb +105 -0
- data/lib/cash/accessor.rb +83 -0
- data/lib/cash/adapter/memcache_client.rb +36 -0
- data/lib/cash/adapter/memcached.rb +127 -0
- data/lib/cash/adapter/redis.rb +144 -0
- data/lib/cash/buffered.rb +137 -0
- data/lib/cash/config.rb +78 -0
- data/lib/cash/fake.rb +83 -0
- data/lib/cash/finders.rb +50 -0
- data/lib/cash/index.rb +211 -0
- data/lib/cash/local.rb +105 -0
- data/lib/cash/lock.rb +63 -0
- data/lib/cash/mock.rb +158 -0
- data/lib/cash/query/abstract.rb +219 -0
- data/lib/cash/query/calculation.rb +45 -0
- data/lib/cash/query/primary_key.rb +50 -0
- data/lib/cash/query/select.rb +16 -0
- data/lib/cash/request.rb +3 -0
- data/lib/cash/transactional.rb +43 -0
- data/lib/cash/util/array.rb +9 -0
- data/lib/cash/util/marshal.rb +19 -0
- data/lib/cash/version.rb +3 -0
- data/lib/cash/write_through.rb +71 -0
- data/lib/mem_cached_session_store.rb +49 -0
- data/lib/mem_cached_support_store.rb +143 -0
- data/rails/init.rb +1 -0
- data/spec/cash/accessor_spec.rb +186 -0
- data/spec/cash/active_record_spec.rb +224 -0
- data/spec/cash/buffered_spec.rb +9 -0
- data/spec/cash/calculations_spec.rb +78 -0
- data/spec/cash/finders_spec.rb +455 -0
- data/spec/cash/local_buffer_spec.rb +9 -0
- data/spec/cash/local_spec.rb +9 -0
- data/spec/cash/lock_spec.rb +110 -0
- data/spec/cash/marshal_spec.rb +60 -0
- data/spec/cash/order_spec.rb +172 -0
- data/spec/cash/transactional_spec.rb +602 -0
- data/spec/cash/window_spec.rb +195 -0
- data/spec/cash/without_caching_spec.rb +32 -0
- data/spec/cash/write_through_spec.rb +252 -0
- data/spec/spec_helper.rb +87 -0
- metadata +300 -0
data/TODO
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
TOP PRIORITY
|
2
|
+
|
3
|
+
REFACTOR
|
4
|
+
* Clarify terminology around cache/key/index, etc.
|
5
|
+
|
6
|
+
INFRASTRUCTURE
|
7
|
+
|
8
|
+
NEW FEATURES
|
9
|
+
* transactional get multi isn't really multi
|
10
|
+
|
11
|
+
BUGS
|
12
|
+
* Handle append strategy (using add rather than set?) to avoid race condition
|
13
|
+
|
14
|
+
MISSING TESTS:
|
15
|
+
* missing tests for Klass.transaction do ... end
|
16
|
+
* non "id" pks work but lack test coverage
|
17
|
+
* expire_cache
|
@@ -0,0 +1,13 @@
|
|
1
|
+
* does not work with :dependent => nullify because
|
2
|
+
def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
|
3
|
+
association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
|
4
|
+
end
|
5
|
+
This does not trigger callbacks
|
6
|
+
* update_all, delete, update_counter, increment_counter, decrement_counter, counter_caches in general - counter caches are replaced by this gem, bear that in mind.
|
7
|
+
* attr_readonly - no technical obstacle, just not yet supported
|
8
|
+
* attributes before typecast behave unpredictably - hard to support
|
9
|
+
* Named bind variables :conditions => ["name = :name", { :name => "37signals!" }] - not hard to support
|
10
|
+
* printf style binds: :conditions => ["name = '%s'", "37signals!"] - not too hard to support
|
11
|
+
* objects as attributes that are serialized. story.title = {:foo => :bar}; customer.balance = Money.new(...) - these could be coerced using Column#type_cast?
|
12
|
+
|
13
|
+
With a lot of these features the issue is not technical but performance. Every special case costs some overhead.
|
data/db/schema.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 2) do
|
2
|
+
create_table "stories", :force => true do |t|
|
3
|
+
t.string "title", "subtitle"
|
4
|
+
t.string "type"
|
5
|
+
t.boolean "published"
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table "characters", :force => true do |t|
|
9
|
+
t.integer "story_id"
|
10
|
+
t.string "name"
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :sessions, :force => true do |t|
|
14
|
+
t.string :session_id
|
15
|
+
t.text :data
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rails/init'
|
data/lib/cache_money.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
require 'cash/version'
|
5
|
+
require 'cash/lock'
|
6
|
+
require 'cash/transactional'
|
7
|
+
require 'cash/write_through'
|
8
|
+
require 'cash/finders'
|
9
|
+
require 'cash/buffered'
|
10
|
+
require 'cash/index'
|
11
|
+
require 'cash/config'
|
12
|
+
require 'cash/accessor'
|
13
|
+
|
14
|
+
require 'cash/request'
|
15
|
+
require 'cash/fake'
|
16
|
+
require 'cash/local'
|
17
|
+
|
18
|
+
require 'cash/query/abstract'
|
19
|
+
require 'cash/query/select'
|
20
|
+
require 'cash/query/primary_key'
|
21
|
+
require 'cash/query/calculation'
|
22
|
+
|
23
|
+
require 'cash/util/array'
|
24
|
+
require 'cash/util/marshal'
|
25
|
+
|
26
|
+
module Cash
|
27
|
+
mattr_accessor :enabled
|
28
|
+
self.enabled = true
|
29
|
+
|
30
|
+
mattr_accessor :repository
|
31
|
+
|
32
|
+
def self.configure(options = {})
|
33
|
+
options.assert_valid_keys(:repository, :local, :transactional, :adapter, :default_ttl)
|
34
|
+
cache = options[:repository] || raise(":repository is a required option")
|
35
|
+
|
36
|
+
adapter = options.fetch(:adapter, :memcached)
|
37
|
+
|
38
|
+
if adapter
|
39
|
+
require "cash/adapter/#{adapter.to_s}"
|
40
|
+
klass = "Cash::Adapter::#{adapter.to_s.camelize}".constantize
|
41
|
+
cache = klass.new(cache, :logger => Rails.logger, :default_ttl => options.fetch(:default_ttl, 1.day.to_i))
|
42
|
+
end
|
43
|
+
|
44
|
+
lock = Cash::Lock.new(cache)
|
45
|
+
cache = Cash::Local.new(cache) if options.fetch(:local, true)
|
46
|
+
cache = Cash::Transactional.new(cache, lock) if options.fetch(:transactional, true)
|
47
|
+
|
48
|
+
self.repository = cache
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.included(active_record_class)
|
52
|
+
active_record_class.class_eval do
|
53
|
+
include Config, Accessor, WriteThrough, Finders
|
54
|
+
extend ClassMethods
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def self.repository
|
61
|
+
@@repository || raise("Cash.configure must be called when Cash.enabled is true")
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
def self.extended(active_record_class)
|
66
|
+
class << active_record_class
|
67
|
+
alias_method_chain :transaction, :cache_transaction
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def transaction_with_cache_transaction(*args, &block)
|
72
|
+
if Cash.enabled
|
73
|
+
# Wrap both the db and cache transaction in another cache transaction so that the cache
|
74
|
+
# gets written only after the database commit but can still flush the inner cache
|
75
|
+
# transaction if an AR::Rollback is issued.
|
76
|
+
Cash.repository.transaction do
|
77
|
+
transaction_without_cache_transaction(*args) do
|
78
|
+
Cash.repository.transaction { block.call }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
else
|
82
|
+
transaction_without_cache_transaction(*args, &block)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class ActiveRecord::Base
|
89
|
+
include Cash
|
90
|
+
|
91
|
+
def self.is_cached(options = {})
|
92
|
+
options.assert_valid_keys(:ttl, :repository, :version)
|
93
|
+
opts = options.dup
|
94
|
+
opts[:repository] = Cash.repository unless opts.has_key?(:repository)
|
95
|
+
Cash::Config.create(self, opts)
|
96
|
+
end
|
97
|
+
|
98
|
+
def <=>(other)
|
99
|
+
if self.id == other.id then
|
100
|
+
0
|
101
|
+
else
|
102
|
+
self.id < other.id ? -1 : 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Cash
|
2
|
+
module Accessor
|
3
|
+
def self.included(a_module)
|
4
|
+
a_module.module_eval do
|
5
|
+
extend ClassMethods
|
6
|
+
include InstanceMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def fetch(keys, options = {}, &block)
|
12
|
+
case keys
|
13
|
+
when Array
|
14
|
+
return {} if keys.empty?
|
15
|
+
|
16
|
+
keys = keys.collect { |key| cache_key(key) }
|
17
|
+
hits = repository.get_multi(*keys)
|
18
|
+
if (missed_keys = keys - hits.keys).any?
|
19
|
+
missed_values = block.call(missed_keys)
|
20
|
+
hits.merge!(missed_keys.zip(Array(missed_values)).to_hash_without_nils)
|
21
|
+
end
|
22
|
+
hits
|
23
|
+
else
|
24
|
+
repository.get(cache_key(keys), options[:raw]) || (block ? block.call : nil)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(keys, options = {}, &block)
|
29
|
+
case keys
|
30
|
+
when Array
|
31
|
+
fetch(keys, options, &block)
|
32
|
+
else
|
33
|
+
fetch(keys, options) do
|
34
|
+
if block_given?
|
35
|
+
add(keys, result = yield(keys), options)
|
36
|
+
result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def add(key, value, options = {})
|
43
|
+
if repository.add(cache_key(key), value, options[:ttl] || cache_config.ttl, options[:raw]) == "NOT_STORED\r\n"
|
44
|
+
yield if block_given?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def set(key, value, options = {})
|
49
|
+
repository.set(cache_key(key), value, options[:ttl] || cache_config.ttl, options[:raw])
|
50
|
+
end
|
51
|
+
|
52
|
+
def incr(key, delta = 1, ttl = nil)
|
53
|
+
ttl ||= cache_config.ttl
|
54
|
+
repository.incr(cache_key = cache_key(key), delta) || begin
|
55
|
+
repository.add(cache_key, (result = yield).to_s, ttl, true) { repository.incr(cache_key) }
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def decr(key, delta = 1, ttl = nil)
|
61
|
+
ttl ||= cache_config.ttl
|
62
|
+
repository.decr(cache_key = cache_key(key), delta) || begin
|
63
|
+
repository.add(cache_key, (result = yield).to_s, ttl, true) { repository.decr(cache_key) }
|
64
|
+
result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def expire(key)
|
69
|
+
repository.delete(cache_key(key))
|
70
|
+
end
|
71
|
+
|
72
|
+
def cache_key(key)
|
73
|
+
"#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module InstanceMethods
|
78
|
+
def expire
|
79
|
+
self.class.expire(id)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'memcache'
|
2
|
+
|
3
|
+
module Cash
|
4
|
+
module Adapter
|
5
|
+
class MemcacheClient
|
6
|
+
def initialize(repository, options = {})
|
7
|
+
@repository = repository
|
8
|
+
@logger = options[:logger]
|
9
|
+
@default_ttl = options[:default_ttl] || raise(":default_ttl is a required option")
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(key, value, ttl=nil, raw=false)
|
13
|
+
@repository.add(key, value || @default_ttl, ttl, raw)
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(key, value, ttl=nil, raw=false)
|
17
|
+
@repository.set(key, value || @default_ttl, ttl, raw)
|
18
|
+
end
|
19
|
+
|
20
|
+
def exception_classes
|
21
|
+
MemCache::MemCacheError
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_to?(method)
|
25
|
+
super || @repository.respond_to?(method)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def method_missing(*args, &block)
|
31
|
+
@repository.send(*args, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'memcached'
|
2
|
+
|
3
|
+
# Maps memcached methods and semantics to those of memcache-client
|
4
|
+
module Cash
|
5
|
+
module Adapter
|
6
|
+
class Memcached
|
7
|
+
def initialize(repository, options = {})
|
8
|
+
@repository = repository
|
9
|
+
@logger = options[:logger]
|
10
|
+
@default_ttl = options[:default_ttl] || raise(":default_ttl is a required option")
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(key, value, ttl=nil, raw=false)
|
14
|
+
wrap(key, not_stored) do
|
15
|
+
logger.debug("Memcached add: #{key.inspect}") if debug_logger?
|
16
|
+
@repository.add(key, raw ? value.to_s : value, ttl || @default_ttl, !raw)
|
17
|
+
logger.debug("Memcached hit: #{key.inspect}") if debug_logger?
|
18
|
+
stored
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Wraps Memcached#get so that it doesn't raise. This has the side-effect of preventing you from
|
23
|
+
# storing <tt>nil</tt> values.
|
24
|
+
def get(key, raw=false)
|
25
|
+
wrap(key) do
|
26
|
+
logger.debug("Memcached get: #{key.inspect}") if debug_logger?
|
27
|
+
value = wrap(key) { @repository.get(key, !raw) }
|
28
|
+
logger.debug("Memcached hit: #{key.inspect}") if debug_logger?
|
29
|
+
value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_multi(*keys)
|
34
|
+
wrap(keys, {}) do
|
35
|
+
begin
|
36
|
+
keys.flatten!
|
37
|
+
logger.debug("Memcached get_multi: #{keys.inspect}") if debug_logger?
|
38
|
+
values = @repository.get(keys, true)
|
39
|
+
logger.debug("Memcached hit: #{keys.inspect}") if debug_logger?
|
40
|
+
values
|
41
|
+
rescue TypeError
|
42
|
+
log_error($!) if logger
|
43
|
+
keys.each { |key| delete(key) }
|
44
|
+
logger.debug("Memcached deleted: #{keys.inspect}") if debug_logger?
|
45
|
+
{}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def set(key, value, ttl=nil, raw=false)
|
51
|
+
wrap(key, not_stored) do
|
52
|
+
logger.debug("Memcached set: #{key.inspect}") if debug_logger?
|
53
|
+
@repository.set(key, raw ? value.to_s : value, ttl || @default_ttl, !raw)
|
54
|
+
logger.debug("Memcached hit: #{key.inspect}") if debug_logger?
|
55
|
+
stored
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete(key)
|
60
|
+
wrap(key, not_found) do
|
61
|
+
logger.debug("Memcached delete: #{key.inspect}") if debug_logger?
|
62
|
+
@repository.delete(key)
|
63
|
+
logger.debug("Memcached hit: #{key.inspect}") if debug_logger?
|
64
|
+
deleted
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def incr(key, value = 1)
|
69
|
+
wrap(key) { @repository.incr(key, value) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def decr(key, value = 1)
|
73
|
+
wrap(key) { @repository.decr(key, value) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def flush_all
|
77
|
+
@repository.flush
|
78
|
+
end
|
79
|
+
|
80
|
+
def exception_classes
|
81
|
+
::Memcached::Error
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def logger
|
87
|
+
@logger
|
88
|
+
end
|
89
|
+
|
90
|
+
def debug_logger?
|
91
|
+
logger && logger.respond_to?(:debug?) && logger.debug?
|
92
|
+
end
|
93
|
+
|
94
|
+
def wrap(key, error_value = nil, options = {})
|
95
|
+
yield
|
96
|
+
rescue ::Memcached::NotStored, ::Memcached::NotFound
|
97
|
+
logger.debug("Memcached miss: #{key.inspect}") if debug_logger?
|
98
|
+
error_value
|
99
|
+
rescue ::Memcached::Error
|
100
|
+
log_error($!) if logger
|
101
|
+
raise if options[:reraise_error]
|
102
|
+
error_value
|
103
|
+
end
|
104
|
+
|
105
|
+
def stored
|
106
|
+
"STORED\r\n"
|
107
|
+
end
|
108
|
+
|
109
|
+
def deleted
|
110
|
+
"DELETED\r\n"
|
111
|
+
end
|
112
|
+
|
113
|
+
def not_stored
|
114
|
+
"NOT_STORED\r\n"
|
115
|
+
end
|
116
|
+
|
117
|
+
def not_found
|
118
|
+
"NOT_FOUND\r\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
def log_error(err)
|
122
|
+
#logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
|
123
|
+
logger.error("Memcached ERROR, #{err.class}: #{err}") if logger
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,144 @@
|
|
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 incr(key, value = 1)
|
77
|
+
# Redis always answeres positively to incr/decr but memcache does not and waits for the key
|
78
|
+
# to be added in a separate operation.
|
79
|
+
if wrap(nil) { @repository.exists(key) }
|
80
|
+
wrap(key) { @repository.incrby(key, value).to_i }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def decr(key, value = 1)
|
85
|
+
if wrap(nil) { @repository.exists(key) }
|
86
|
+
wrap(key) { @repository.decrby(key, value).to_i }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def flush_all
|
91
|
+
@repository.flushall
|
92
|
+
end
|
93
|
+
|
94
|
+
def exception_classes
|
95
|
+
[Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL]
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def logger
|
101
|
+
@logger
|
102
|
+
end
|
103
|
+
|
104
|
+
def debug_logger?
|
105
|
+
logger && logger.respond_to?(:debug?) && logger.debug?
|
106
|
+
end
|
107
|
+
|
108
|
+
def wrap(key, error_value = nil)
|
109
|
+
yield
|
110
|
+
rescue *exception_classes
|
111
|
+
log_error($!) if logger
|
112
|
+
error_value
|
113
|
+
end
|
114
|
+
|
115
|
+
def dump(value)
|
116
|
+
Marshal.dump(value)
|
117
|
+
end
|
118
|
+
|
119
|
+
def load(value)
|
120
|
+
Marshal.load(value)
|
121
|
+
end
|
122
|
+
|
123
|
+
def stored
|
124
|
+
"STORED\r\n"
|
125
|
+
end
|
126
|
+
|
127
|
+
def deleted
|
128
|
+
"DELETED\r\n"
|
129
|
+
end
|
130
|
+
|
131
|
+
def not_stored
|
132
|
+
"NOT_STORED\r\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
def not_found
|
136
|
+
"NOT_FOUND\r\n"
|
137
|
+
end
|
138
|
+
|
139
|
+
def log_error(err)
|
140
|
+
logger.error("Redis ERROR, #{err.class}: #{err}") if logger
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|