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 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