viximo-cache-money 0.3.0
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.
- 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
|