horcrux 0.0.4 → 0.1.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/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
|