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 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.4'
16
- s.date = '2012-01-03'
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
@@ -1,6 +1,6 @@
1
1
  # See the README.md
2
2
  module Horcrux
3
- VERSION = "0.0.4"
3
+ VERSION = "0.1.0"
4
4
 
5
5
  # Implements the optional methods of a Horcrux adapter.
6
6
  module Methods
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
- version: 0.0.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
- date: 2012-01-03 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2012-01-09 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: rake
16
- requirement: &70162015702560 !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
17
24
  none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
22
32
  type: :development
23
- prerelease: false
24
- version_requirements: *70162015702560
25
- - !ruby/object:Gem::Dependency
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
26
35
  name: test-unit
27
- requirement: &70162015699840 !ruby/object:Gem::Requirement
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
28
38
  none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: '0'
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
33
46
  type: :development
34
- prerelease: false
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
- files:
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
- require_paths:
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
- version: '0'
67
- required_rubygems_version: !ruby/object:Gem::Requirement
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.11
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