horcrux 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/horcrux.gemspec +4 -2
- data/lib/horcrux/multiple.rb +206 -0
- data/lib/horcrux.rb +1 -1
- data/test/helper.rb +1 -0
- data/test/multiple_test.rb +120 -0
- metadata +64 -35
data/horcrux.gemspec
CHANGED
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
|
|
12
12
|
## If your rubyforge_project name is different, then edit it and comment out
|
13
13
|
## the sub! line in the Rakefile
|
14
14
|
s.name = 'horcrux'
|
15
|
-
s.version = '0.0
|
16
|
-
s.date = '2012-01-
|
15
|
+
s.version = '0.1.0'
|
16
|
+
s.date = '2012-01-09'
|
17
17
|
s.rubyforge_project = 'horcrux'
|
18
18
|
|
19
19
|
## Make sure your summary is short. The description may be as long
|
@@ -47,11 +47,13 @@ Gem::Specification.new do |s|
|
|
47
47
|
horcrux.gemspec
|
48
48
|
lib/horcrux.rb
|
49
49
|
lib/horcrux/entity.rb
|
50
|
+
lib/horcrux/multiple.rb
|
50
51
|
lib/horcrux/serializers/gzip_serializer.rb
|
51
52
|
lib/horcrux/serializers/message_pack_serializer.rb
|
52
53
|
test/entity_test.rb
|
53
54
|
test/helper.rb
|
54
55
|
test/memory_test.rb
|
56
|
+
test/multiple_test.rb
|
55
57
|
test/serializer_test.rb
|
56
58
|
]
|
57
59
|
# = MANIFEST =
|
@@ -0,0 +1,206 @@
|
|
1
|
+
module Horcrux
|
2
|
+
class Multiple
|
3
|
+
include Methods
|
4
|
+
|
5
|
+
attr_reader :rescuable_exceptions
|
6
|
+
attr_reader :error_handlers, :missing_handlers
|
7
|
+
|
8
|
+
# Sets up an Adapter using a collection of other adapters. The first is
|
9
|
+
# assumed to be the main, while the others are write-through caches. This
|
10
|
+
# is good for caching.
|
11
|
+
#
|
12
|
+
# mysql = Horcrux::MysqlAdapter.new ... # fake
|
13
|
+
# memcache = Horcrux::MemcacheAdapter.new ... # fake
|
14
|
+
# adapter = Horcrux::Multiple.new mysql, memcache
|
15
|
+
#
|
16
|
+
# Reads will hit the secondary adapters before the main. Writes will hit
|
17
|
+
# the main adapter first, before being sent to the secondary adapters.
|
18
|
+
#
|
19
|
+
# *adapters - One or more Horcrux-compliant adapters.
|
20
|
+
def initialize(*adapters)
|
21
|
+
if adapters.empty?
|
22
|
+
raise ArgumentError, "Need at least 1 adapter."
|
23
|
+
end
|
24
|
+
|
25
|
+
@main = adapters.shift
|
26
|
+
@adapters = adapters
|
27
|
+
@error_handlers = []
|
28
|
+
@missing_handlers = []
|
29
|
+
@rescuable_exceptions = [StandardError]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Public: Adds the given block to the chain of handlers to call for a
|
33
|
+
# raised exception while accessing one of the adapters.
|
34
|
+
#
|
35
|
+
# @adapter.on_error do |err, obj|
|
36
|
+
# obj[:adapter]
|
37
|
+
# obj[:method]
|
38
|
+
# obj[:args]
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Returns nothing.
|
42
|
+
def on_error(&block)
|
43
|
+
@error_handlers << block
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Adds the given block to the chain of handlers to call when a
|
48
|
+
# secondary adapter is missing one or more keys.
|
49
|
+
#
|
50
|
+
# @adapter.on_missing do |adapter, values|
|
51
|
+
# adapter.set_all(values)
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# Returns nothing.
|
55
|
+
def on_missing(&block)
|
56
|
+
@missing_handlers << block
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
## HORCRUX METHODS
|
61
|
+
|
62
|
+
def get_all(*keys)
|
63
|
+
original = keys.dup
|
64
|
+
adapter_missing = {}
|
65
|
+
values = {}
|
66
|
+
|
67
|
+
@adapters.each do |adapter|
|
68
|
+
found, missing = get_from_adapter(adapter, keys)
|
69
|
+
values.update(found)
|
70
|
+
|
71
|
+
if !missing.empty?
|
72
|
+
adapter_missing[adapter] = missing
|
73
|
+
end
|
74
|
+
|
75
|
+
keys = missing
|
76
|
+
end
|
77
|
+
|
78
|
+
found, missing = get_from_adapter(@main, keys)
|
79
|
+
values.update(found)
|
80
|
+
|
81
|
+
call_missing_handlers(values, adapter_missing)
|
82
|
+
|
83
|
+
original.map { |key| values[key] }
|
84
|
+
end
|
85
|
+
|
86
|
+
def key?(key)
|
87
|
+
read_cache :key?, key
|
88
|
+
end
|
89
|
+
|
90
|
+
def get(key)
|
91
|
+
read_cache :get, key
|
92
|
+
end
|
93
|
+
|
94
|
+
def set(key, value)
|
95
|
+
write_through :set, key, value
|
96
|
+
end
|
97
|
+
|
98
|
+
def set_all(values)
|
99
|
+
write_through :set_all, values
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete(key)
|
103
|
+
write_through :delete, key
|
104
|
+
end
|
105
|
+
|
106
|
+
def delete_all(*keys)
|
107
|
+
write_through :delete_all, *keys
|
108
|
+
end
|
109
|
+
|
110
|
+
## INTERNAL
|
111
|
+
|
112
|
+
# Writes the data to the main adapter first, and then to the other
|
113
|
+
# adapters.
|
114
|
+
#
|
115
|
+
# method - A Symbol identifying the method to call.
|
116
|
+
# *args - One or more arguments to send to the methods.
|
117
|
+
#
|
118
|
+
# Returns the result of the method call on the main adapter.
|
119
|
+
def write_through(method, *args)
|
120
|
+
result = @main.send(method, *args)
|
121
|
+
@adapters.each do |adapter|
|
122
|
+
call_adapter adapter, method, *args
|
123
|
+
end
|
124
|
+
result
|
125
|
+
end
|
126
|
+
|
127
|
+
# Reads the data from the other adapters before the main adapter.
|
128
|
+
#
|
129
|
+
# method - A Symbol identifying the method to call.
|
130
|
+
# *args - One or more arguments to send to the methods.
|
131
|
+
#
|
132
|
+
# Returns the result of the first adapter to respond with a value.
|
133
|
+
def read_cache(method, *args)
|
134
|
+
value = nil
|
135
|
+
@adapters.detect do |adapter|
|
136
|
+
value = call_adapter adapter, method, *args
|
137
|
+
end
|
138
|
+
value || @main.send(method, *args)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Calls the given adapter, swallowing up any error.
|
142
|
+
#
|
143
|
+
# adapter - A Horcrux adapter.
|
144
|
+
# method - A Symbol identifying the method to call.
|
145
|
+
# *args - One or more arguments to send to the methods.
|
146
|
+
#
|
147
|
+
# Returns the value of the method call.
|
148
|
+
def call_adapter(adapter, method, *args)
|
149
|
+
adapter.send(method, *args)
|
150
|
+
rescue Object => err
|
151
|
+
raise unless @rescuable_exceptions.any? { |klass| err.is_a?(klass) }
|
152
|
+
|
153
|
+
if @error_handlers.each do |handler|
|
154
|
+
handler.call err, :adapter => adapter, :method => method, :args => args
|
155
|
+
end.empty?
|
156
|
+
$stderr.puts "#{err.class} Exception for #{adapter.inspect}##{method}: #{err}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Gets all keys from the adapter.
|
161
|
+
#
|
162
|
+
# adapter - A Horcrux adapter.
|
163
|
+
# keys - Array of String keys to fetch.
|
164
|
+
#
|
165
|
+
# Returns an Array tuple with a Hash of found keys/values, and an Array of
|
166
|
+
# missing keys.
|
167
|
+
def get_from_adapter(adapter, keys)
|
168
|
+
missing = []
|
169
|
+
found = {}
|
170
|
+
|
171
|
+
adapter.get_all(*keys).each_with_index do |value, index|
|
172
|
+
key = keys[index]
|
173
|
+
|
174
|
+
if value
|
175
|
+
found[key] = value
|
176
|
+
else
|
177
|
+
missing << key
|
178
|
+
end
|
179
|
+
end unless keys.empty?
|
180
|
+
|
181
|
+
[found, missing]
|
182
|
+
end
|
183
|
+
|
184
|
+
# Call the on_missing callbacks for the handlers that were missing keys.
|
185
|
+
# This gives you a chance to set those values in the secondary adapters.
|
186
|
+
#
|
187
|
+
# values - A Hash of all of the found keys => values.
|
188
|
+
# missing - A Hash of Adapter => Array of missing keys.
|
189
|
+
#
|
190
|
+
# Returns nothing.
|
191
|
+
def call_missing_handlers(values, missing)
|
192
|
+
return if @missing_handlers.empty?
|
193
|
+
missing.each do |adapter, keys|
|
194
|
+
missing_values = {}
|
195
|
+
keys.each do |key|
|
196
|
+
missing_values[key] = values[key]
|
197
|
+
end
|
198
|
+
|
199
|
+
@missing_handlers.each do |handler|
|
200
|
+
handler.call adapter, missing_values
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
data/lib/horcrux.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -2,4 +2,5 @@ require 'test/unit'
|
|
2
2
|
require File.expand_path('../../lib/horcrux', __FILE__)
|
3
3
|
require File.expand_path('../../lib/horcrux/serializers/gzip_serializer', __FILE__)
|
4
4
|
require File.expand_path('../../lib/horcrux/entity', __FILE__)
|
5
|
+
require File.expand_path('../../lib/horcrux/multiple', __FILE__)
|
5
6
|
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require File.expand_path('../memory_test', __FILE__)
|
2
|
+
|
3
|
+
module Horcrux
|
4
|
+
class MultipleTest < MemoryTest
|
5
|
+
def setup
|
6
|
+
@main = Memory.new(@main_hash = {})
|
7
|
+
@cache1 = Memory.new(@cache1_hash = {})
|
8
|
+
@cache2 = Memory.new(@cache2_hash = {})
|
9
|
+
@adapter = Multiple.new @main, @cache1, @cache2
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_reads_cache_before_main
|
13
|
+
@main.set 'a', '3'
|
14
|
+
@cache1.set 'a', '1'
|
15
|
+
@cache2.set 'a', '2'
|
16
|
+
|
17
|
+
assert_equal '1', @adapter.get('a')
|
18
|
+
|
19
|
+
@cache1.delete 'a'
|
20
|
+
|
21
|
+
assert_equal '2', @adapter.get('a')
|
22
|
+
|
23
|
+
@cache2.delete 'a'
|
24
|
+
|
25
|
+
assert_equal '3', @adapter.get('a')
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_sets_to_all_caches
|
29
|
+
@adapter.set 'a', '5'
|
30
|
+
|
31
|
+
assert_equal '5', @main.get('a')
|
32
|
+
assert_equal '5', @cache1.get('a')
|
33
|
+
assert_equal '5', @cache2.get('a')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_sets_values_to_all_caches
|
37
|
+
@adapter.set_all 'a' => '5'
|
38
|
+
|
39
|
+
assert_equal '5', @main.get('a')
|
40
|
+
assert_equal '5', @cache1.get('a')
|
41
|
+
assert_equal '5', @cache2.get('a')
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_deletes_from_all_caches
|
45
|
+
@main.set 'a', '1'
|
46
|
+
@cache1.set 'a', '1'
|
47
|
+
@cache2.set 'a', '1'
|
48
|
+
|
49
|
+
@adapter.delete 'a'
|
50
|
+
|
51
|
+
assert !@main.key?('a')
|
52
|
+
assert !@cache1.key?('a')
|
53
|
+
assert !@cache1.key?('a')
|
54
|
+
assert !@adapter.key?('a')
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_deletes_keys_from_all_caches
|
58
|
+
@main.set 'a', '1'
|
59
|
+
@cache1.set 'a', '1'
|
60
|
+
@cache2.set 'a', '1'
|
61
|
+
|
62
|
+
@adapter.delete_all 'a'
|
63
|
+
|
64
|
+
assert !@main.key?('a')
|
65
|
+
assert !@cache1.key?('a')
|
66
|
+
assert !@cache1.key?('a')
|
67
|
+
assert !@adapter.key?('a')
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_calls_on_missing_callback
|
71
|
+
called = 0
|
72
|
+
|
73
|
+
@adapter.on_missing do |adapter, values|
|
74
|
+
called += 1
|
75
|
+
|
76
|
+
case adapter
|
77
|
+
when @cache1
|
78
|
+
assert_equal 2, values.size
|
79
|
+
assert_equal '1', values['a']
|
80
|
+
assert_equal '3', values['c']
|
81
|
+
when @cache2
|
82
|
+
assert_equal 1, values.size
|
83
|
+
assert_equal '3', values['c']
|
84
|
+
else
|
85
|
+
fail "Bad adapter: #{adapter.inspect}"
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
@cache2.set 'a', '1'
|
91
|
+
@cache1.set 'b', '2'
|
92
|
+
@main.set 'c', '3'
|
93
|
+
|
94
|
+
assert_equal %w(1 2 3), @adapter.get_all('a', 'b', 'c')
|
95
|
+
assert_equal 2, called
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_ignores_errors
|
99
|
+
called = false
|
100
|
+
|
101
|
+
@adapter.on_error do |err, obj|
|
102
|
+
called = true
|
103
|
+
assert_kind_of RuntimeError, err
|
104
|
+
assert_equal @cache1, obj[:adapter]
|
105
|
+
assert_equal :get, obj[:method]
|
106
|
+
assert_equal %w(a), obj[:args]
|
107
|
+
end
|
108
|
+
|
109
|
+
hash = @cache1_hash
|
110
|
+
def hash.[](*args) raise end
|
111
|
+
|
112
|
+
@cache1.set 'a', '1'
|
113
|
+
@cache2.set 'a', '2'
|
114
|
+
|
115
|
+
assert_equal '2', @adapter.get('a')
|
116
|
+
assert called
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
metadata
CHANGED
@@ -1,44 +1,59 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: horcrux
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Rick Olson
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2012-01-09 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
15
21
|
name: rake
|
16
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
17
24
|
none: false
|
18
|
-
requirements:
|
19
|
-
- -
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
22
32
|
type: :development
|
23
|
-
|
24
|
-
|
25
|
-
- !ruby/object:Gem::Dependency
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
26
35
|
name: test-unit
|
27
|
-
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
28
38
|
none: false
|
29
|
-
requirements:
|
30
|
-
- -
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
33
46
|
type: :development
|
34
|
-
|
35
|
-
version_requirements: *70162015699840
|
47
|
+
version_requirements: *id002
|
36
48
|
description: Simple key/value adapter library.
|
37
49
|
email: technoweenie@gmail.com
|
38
50
|
executables: []
|
51
|
+
|
39
52
|
extensions: []
|
53
|
+
|
40
54
|
extra_rdoc_files: []
|
41
|
-
|
55
|
+
|
56
|
+
files:
|
42
57
|
- Gemfile
|
43
58
|
- LICENSE.md
|
44
59
|
- README.md
|
@@ -46,38 +61,52 @@ files:
|
|
46
61
|
- horcrux.gemspec
|
47
62
|
- lib/horcrux.rb
|
48
63
|
- lib/horcrux/entity.rb
|
64
|
+
- lib/horcrux/multiple.rb
|
49
65
|
- lib/horcrux/serializers/gzip_serializer.rb
|
50
66
|
- lib/horcrux/serializers/message_pack_serializer.rb
|
51
67
|
- test/entity_test.rb
|
52
68
|
- test/helper.rb
|
53
69
|
- test/memory_test.rb
|
70
|
+
- test/multiple_test.rb
|
54
71
|
- test/serializer_test.rb
|
55
72
|
homepage: http://github.com/technoweenie/horcrux
|
56
73
|
licenses: []
|
74
|
+
|
57
75
|
post_install_message:
|
58
76
|
rdoc_options: []
|
59
|
-
|
77
|
+
|
78
|
+
require_paths:
|
60
79
|
- lib
|
61
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
81
|
none: false
|
63
|
-
requirements:
|
64
|
-
- -
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
|
67
|
-
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
90
|
none: false
|
69
|
-
requirements:
|
70
|
-
- -
|
71
|
-
- !ruby/object:Gem::Version
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 17
|
95
|
+
segments:
|
96
|
+
- 1
|
97
|
+
- 3
|
98
|
+
- 5
|
72
99
|
version: 1.3.5
|
73
100
|
requirements: []
|
101
|
+
|
74
102
|
rubyforge_project: horcrux
|
75
|
-
rubygems_version: 1.8.
|
103
|
+
rubygems_version: 1.8.10
|
76
104
|
signing_key:
|
77
105
|
specification_version: 2
|
78
106
|
summary: Simple key/value adapter library.
|
79
|
-
test_files:
|
107
|
+
test_files:
|
80
108
|
- test/entity_test.rb
|
81
109
|
- test/helper.rb
|
82
110
|
- test/memory_test.rb
|
111
|
+
- test/multiple_test.rb
|
83
112
|
- test/serializer_test.rb
|