adapter 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +9 -0
- data/README.rdoc +59 -0
- data/Rakefile +7 -0
- data/lib/adapter.rb +58 -0
- data/lib/adapter/asserts.rb +21 -0
- data/lib/adapter/defaults.rb +33 -0
- data/lib/adapter/exceptions.rb +17 -0
- data/lib/adapter/memory.rb +23 -0
- data/lib/adapter/version.rb +3 -0
- data/spec/adapter/defaults_spec.rb +40 -0
- data/spec/adapter/memory_spec.rb +15 -0
- data/spec/adapter_spec.rb +302 -0
- data/spec/helper.rb +28 -0
- data/spec/spec.opts +3 -0
- data/spec/support/an_adapter.rb +162 -0
- data/spec/support/json_adapter.rb +10 -0
- data/spec/support/marshal_adapter.rb +12 -0
- data/spec/support/module_helpers.rb +29 -0
- metadata +84 -0
data/LICENSE
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright (c) 2011, Zynga Inc.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
5
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
6
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
7
|
+
* Neither the name of Zynga Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
8
|
+
|
9
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
= Adapter
|
2
|
+
|
3
|
+
A simple interface to anything.
|
4
|
+
|
5
|
+
== Defining an Adapter
|
6
|
+
|
7
|
+
An adapter requires 4 methods to work: read, write, delete and clear.
|
8
|
+
|
9
|
+
Adapter.define(:memory) do
|
10
|
+
def read(key)
|
11
|
+
decode(client[key_for(key)])
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(key, value)
|
15
|
+
client[key_for(key)] = encode(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(key)
|
19
|
+
client.delete(key_for(key))
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear
|
23
|
+
client.clear
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Note: in order for things to be most flexible, always wrap key with key_for(key) which will ensure that pretty much anything can be a key. Also, by default encode and decode are included and they Marshal.dump and Marshal.load. Feel free to override these if you prefer JSON serialization or something else.
|
28
|
+
|
29
|
+
Once you have defined an adapter, you can get a class of that adapter like this:
|
30
|
+
|
31
|
+
Adapter[:memory]
|
32
|
+
|
33
|
+
This returns a dynamic class with your adapting methods included and an initialize method that takes a client. This means you can get an instance of the adapter by using new and passing the client (in this instance a boring hash):
|
34
|
+
|
35
|
+
adapter = Adapter[:memory].new({}) # sets {} to client
|
36
|
+
adapter.write('foo', 'bar')
|
37
|
+
adapter.read('foo') # 'bar'
|
38
|
+
adapter.delete('foo')
|
39
|
+
adapter.fetch('foo', 'bar') # returns bar and sets foo to bar
|
40
|
+
|
41
|
+
get and [] are aliased to read. set and []= are aliased to write.
|
42
|
+
|
43
|
+
Note: You can also optionally provide a lock method. See the memcached and redis adapters for more on locking.
|
44
|
+
|
45
|
+
== Extending Adapters
|
46
|
+
|
47
|
+
Adapters can be defined using a block, a module, or a module and a block. This allows very flexibly overriding specific things in an adapter to create a new adapter, without having to redo all of the work. See examples/overriding_serialization.rb for an example of this.
|
48
|
+
|
49
|
+
== Note on Patches/Pull Requests
|
50
|
+
|
51
|
+
* Fork the project.
|
52
|
+
* Make your feature addition or bug fix.
|
53
|
+
* Add tests for it. This is important so we don't break it in a future version unintentionally.
|
54
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine, but bump version in a commit by itself so we can ignore when we pull)
|
55
|
+
* Send us a pull request. Bonus points for topic branches.
|
56
|
+
|
57
|
+
== Copyright
|
58
|
+
|
59
|
+
Copyright (c) 2010 New Toy. See LICENSE for details.
|
data/Rakefile
ADDED
data/lib/adapter.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'adapter/asserts'
|
2
|
+
require 'adapter/defaults'
|
3
|
+
require 'adapter/exceptions'
|
4
|
+
|
5
|
+
module Adapter
|
6
|
+
extend Asserts
|
7
|
+
include Defaults
|
8
|
+
|
9
|
+
def self.definitions
|
10
|
+
@definitions ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.define(name, mod=nil, &block)
|
14
|
+
definition_module = Module.new
|
15
|
+
definition_module.send(:include, Defaults)
|
16
|
+
definition_module.send(:include, mod) unless mod.nil?
|
17
|
+
definition_module.send(:include, Module.new(&block)) if block_given?
|
18
|
+
assert_valid_module(definition_module)
|
19
|
+
adapters.delete(name.to_sym)
|
20
|
+
definitions[name.to_sym] = definition_module
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.adapters
|
24
|
+
@adapters ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.[](name)
|
28
|
+
assert_valid_adapter(name)
|
29
|
+
adapters[name.to_sym] ||= get_adapter_instance(name)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def self.get_adapter_instance(name)
|
34
|
+
Class.new do
|
35
|
+
attr_reader :client, :options
|
36
|
+
|
37
|
+
def initialize(client, options={})
|
38
|
+
@client = client
|
39
|
+
@options = options
|
40
|
+
end
|
41
|
+
|
42
|
+
include Adapter.definitions[name.to_sym]
|
43
|
+
|
44
|
+
alias get read
|
45
|
+
alias set write
|
46
|
+
|
47
|
+
alias [] read
|
48
|
+
alias []= write
|
49
|
+
|
50
|
+
def eql?(other)
|
51
|
+
self.class.eql?(other.class) && client == other.client
|
52
|
+
end
|
53
|
+
alias == eql?
|
54
|
+
|
55
|
+
class_eval "def name; :#{name} end"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Adapter
|
2
|
+
module Asserts
|
3
|
+
RequiredMethods = [:read, :write, :delete, :clear]
|
4
|
+
|
5
|
+
def assert_valid_module(mod)
|
6
|
+
assert_methods_defined(mod)
|
7
|
+
end
|
8
|
+
|
9
|
+
def assert_valid_adapter(name)
|
10
|
+
raise Undefined.new(name) unless definitions.key?(name.to_sym)
|
11
|
+
end
|
12
|
+
|
13
|
+
def assert_methods_defined(mod)
|
14
|
+
missing_methods = []
|
15
|
+
RequiredMethods.each do |meth|
|
16
|
+
missing_methods << meth unless mod.method_defined?(meth)
|
17
|
+
end
|
18
|
+
raise IncompleteAPI.new(missing_methods) unless missing_methods.empty?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Adapter
|
2
|
+
module Defaults
|
3
|
+
def fetch(key, value=nil, &block)
|
4
|
+
read(key) || begin
|
5
|
+
value = yield(key) if value.nil? && block_given?
|
6
|
+
write(key, value)
|
7
|
+
value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def key?(key)
|
12
|
+
!read(key).nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def key_for(key)
|
16
|
+
if key.is_a?(String)
|
17
|
+
key
|
18
|
+
elsif key.is_a?(Symbol)
|
19
|
+
key.to_s
|
20
|
+
else
|
21
|
+
Marshal.dump(key)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def encode(value)
|
26
|
+
Marshal.dump(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def decode(value)
|
30
|
+
value && Marshal.load(value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Adapter
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
class Undefined < Error; end
|
5
|
+
|
6
|
+
class IncompleteAPI < Error
|
7
|
+
def initialize(methods)
|
8
|
+
super("Missing methods needed to complete API (#{methods.join(', ')})")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class LockTimeout < Error
|
13
|
+
def initialize(key, timeout)
|
14
|
+
super("Timeout on lock #{key} exceeded #{timeout} sec")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'adapter'
|
2
|
+
|
3
|
+
module Adapter
|
4
|
+
module Memory
|
5
|
+
def read(key)
|
6
|
+
decode(client[key_for(key)])
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(key, value)
|
10
|
+
client[key_for(key)] = encode(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def delete(key)
|
14
|
+
read(key).tap { client.delete(key_for(key)) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
client.clear
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Adapter.define(:memory, Adapter::Memory)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Adapter::Defaults do
|
4
|
+
let(:mod) do
|
5
|
+
Module.new.tap do |m|
|
6
|
+
m.extend(Adapter::Defaults)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#key_for" do
|
11
|
+
it "returns value for string" do
|
12
|
+
mod.key_for('foo').should == 'foo'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns string for symbol" do
|
16
|
+
mod.key_for(:foo).should == 'foo'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "marshals anything not a string or symbol" do
|
20
|
+
mod.key_for({'testing' => 'this'}).should == %Q(\004\b{\006\"\ftesting\"\tthis)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#encode" do
|
25
|
+
it "marshals value" do
|
26
|
+
mod.encode(nil).should == "\004\b0"
|
27
|
+
mod.encode({'testing' => 'this'}).should == %Q(\004\b{\006\"\ftesting\"\tthis)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#decode" do
|
32
|
+
it "returns nil if nil" do
|
33
|
+
mod.decode(nil).should be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns marshal load if not nil" do
|
37
|
+
mod.decode(%Q(\004\b{\006\"\ftesting\"\tthis)).should == {'testing' => 'this'}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'adapter/memory'
|
3
|
+
|
4
|
+
describe "Memory adapter" do
|
5
|
+
before do
|
6
|
+
@client = {}
|
7
|
+
@adapter = Adapter[:memory].new(@client)
|
8
|
+
@adapter.clear
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:adapter) { @adapter }
|
12
|
+
let(:client) { @client }
|
13
|
+
|
14
|
+
it_should_behave_like 'a marshaled adapter'
|
15
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
describe Adapter do
|
4
|
+
describe ".definitions" do
|
5
|
+
it "defaults to empty hash" do
|
6
|
+
Adapter.instance_variable_set("@definitions", nil)
|
7
|
+
Adapter.definitions.should == {}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ".define" do
|
12
|
+
describe "with string name" do
|
13
|
+
it "symbolizes string adapter names" do
|
14
|
+
Adapter.define('memory', valid_module)
|
15
|
+
Adapter.definitions.keys.should include(:memory)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "with module" do
|
20
|
+
before do
|
21
|
+
@mod = valid_module
|
22
|
+
Adapter.define(:memory, mod)
|
23
|
+
end
|
24
|
+
let(:mod) { @mod }
|
25
|
+
|
26
|
+
it "adds adapter to definitions" do
|
27
|
+
Adapter.definitions.should have_key(:memory)
|
28
|
+
Adapter.definitions[:memory].should be_instance_of(Module)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "includes the defaults" do
|
32
|
+
Class.new do
|
33
|
+
include Adapter.definitions[:memory]
|
34
|
+
end.tap do |klass|
|
35
|
+
klass.new.respond_to?(:fetch).should be_true
|
36
|
+
klass.new.respond_to?(:key_for, true).should be_true
|
37
|
+
klass.new.respond_to?(:encode, true).should be_true
|
38
|
+
klass.new.respond_to?(:decode, true).should be_true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
[:read, :write, :delete, :clear].each do |method_name|
|
43
|
+
it "raises error if #{method_name} is not defined in module" do
|
44
|
+
mod.send(:undef_method, method_name)
|
45
|
+
|
46
|
+
lambda do
|
47
|
+
Adapter.define(:memory, mod)
|
48
|
+
end.should raise_error(Adapter::IncompleteAPI, "Missing methods needed to complete API (#{method_name})")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "with block" do
|
54
|
+
before do
|
55
|
+
Adapter.define(:memory) do
|
56
|
+
def read(key)
|
57
|
+
client[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
def write(key, value)
|
61
|
+
client[key] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete(key)
|
65
|
+
client.delete(key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear
|
69
|
+
client.clear
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "adds adapter to definitions" do
|
75
|
+
Adapter.definitions.should have_key(:memory)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "modularizes the block" do
|
79
|
+
Adapter.definitions[:memory].should be_instance_of(Module)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "with module and block" do
|
84
|
+
before do
|
85
|
+
Adapter.define(:memory, valid_module) do
|
86
|
+
def clear
|
87
|
+
raise 'Not Implemented'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "includes block after module" do
|
93
|
+
adapter = Adapter[:memory].new({})
|
94
|
+
adapter.write('foo', 'bar')
|
95
|
+
adapter.read('foo').should == 'bar'
|
96
|
+
lambda do
|
97
|
+
adapter.clear
|
98
|
+
end.should raise_error('Not Implemented')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "Overriding encode/decode" do
|
104
|
+
before do
|
105
|
+
Adapter.define(:memory_json, valid_module) do
|
106
|
+
def encode(value)
|
107
|
+
ActiveSupport::JSON.encode(value)
|
108
|
+
end
|
109
|
+
|
110
|
+
def decode(value)
|
111
|
+
ActiveSupport::JSON.decode(value)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
let(:adapter) { Adapter[:memory_json].new({}) }
|
116
|
+
|
117
|
+
it "encodes correctly" do
|
118
|
+
hash = {'foo' => 'bar'}
|
119
|
+
adapter.write('foo', hash)
|
120
|
+
adapter.client['foo'].should == ActiveSupport::JSON.encode(hash)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "decodes correctly" do
|
124
|
+
hash = {'foo' => 'bar'}
|
125
|
+
adapter.client['foo'] = ActiveSupport::JSON.encode(hash)
|
126
|
+
adapter.read('foo').should == hash
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "Redefining an adapter" do
|
131
|
+
before do
|
132
|
+
Adapter.define(:memory, valid_module)
|
133
|
+
Adapter.define(:hash, valid_module)
|
134
|
+
@memoized_memory = Adapter[:memory]
|
135
|
+
@memoized_hash = Adapter[:hash]
|
136
|
+
Adapter.define(:memory, valid_module)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "unmemoizes adapter by name" do
|
140
|
+
Adapter[:memory].should_not equal(@memoized_memory)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "does not unmemoize other adapters" do
|
144
|
+
Adapter[:hash].should equal(@memoized_hash)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe ".[]" do
|
149
|
+
before do
|
150
|
+
Adapter.define(:memory, valid_module)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "returns adapter instance" do
|
154
|
+
adapter = Adapter[:memory].new({})
|
155
|
+
adapter.write('foo', 'bar')
|
156
|
+
adapter.read('foo').should == 'bar'
|
157
|
+
adapter.delete('foo')
|
158
|
+
adapter.read('foo').should be_nil
|
159
|
+
adapter.write('foo', 'bar')
|
160
|
+
adapter.clear
|
161
|
+
adapter.read('foo').should be_nil
|
162
|
+
end
|
163
|
+
|
164
|
+
it "raises error for undefined adapter" do
|
165
|
+
lambda do
|
166
|
+
Adapter[:non_existant]
|
167
|
+
end.should raise_error(Adapter::Undefined)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "memoizes adapter by name" do
|
171
|
+
Adapter[:memory].should equal(Adapter[:memory])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "Adapter" do
|
176
|
+
before do
|
177
|
+
Adapter.define(:memory, valid_module)
|
178
|
+
@client = {}
|
179
|
+
@adapter = Adapter[:memory].new(@client)
|
180
|
+
end
|
181
|
+
let(:adapter) { @adapter }
|
182
|
+
|
183
|
+
describe "#initialize" do
|
184
|
+
it "works with options" do
|
185
|
+
Adapter.define(:memory, valid_module)
|
186
|
+
adapter = Adapter[:memory].new({}, :namespace => 'foo')
|
187
|
+
adapter.options[:namespace].should == 'foo'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "#name" do
|
192
|
+
it "returns adapter name" do
|
193
|
+
adapter.name.should be(:memory)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe "#fetch" do
|
198
|
+
it "returns value if found" do
|
199
|
+
adapter.write('foo', 'bar')
|
200
|
+
adapter.fetch('foo', 'baz').should == 'bar'
|
201
|
+
end
|
202
|
+
|
203
|
+
it "returns value if not found" do
|
204
|
+
adapter.fetch('foo', 'baz').should == 'baz'
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "with block" do
|
208
|
+
it "returns value if key found" do
|
209
|
+
adapter.write('foo', 'bar')
|
210
|
+
adapter.should_not_receive(:write)
|
211
|
+
adapter.fetch('foo') do
|
212
|
+
'baz'
|
213
|
+
end.should == 'bar'
|
214
|
+
end
|
215
|
+
|
216
|
+
it "returns result of block if key not found and writes result to key" do
|
217
|
+
adapter.fetch('foo') do
|
218
|
+
'baz'
|
219
|
+
end.should == 'baz'
|
220
|
+
adapter.fetch('foo').should == 'baz'
|
221
|
+
end
|
222
|
+
|
223
|
+
it "yields key to block" do
|
224
|
+
adapter.fetch('foo') do |key|
|
225
|
+
key
|
226
|
+
end.should == 'foo'
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "#key?" do
|
232
|
+
it "returns true if key is set" do
|
233
|
+
adapter.write('foo', 'bar')
|
234
|
+
adapter.key?('foo').should be_true
|
235
|
+
end
|
236
|
+
|
237
|
+
it "returns false if key is not set" do
|
238
|
+
adapter.key?('foo').should be_false
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe "#[]" do
|
243
|
+
it "is aliased to read" do
|
244
|
+
adapter.write('foo', 'bar')
|
245
|
+
adapter['foo'].should == 'bar'
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe "#get" do
|
250
|
+
it "is aliased to read" do
|
251
|
+
adapter.write('foo', 'bar')
|
252
|
+
adapter.get('foo').should == 'bar'
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe "#[]=" do
|
257
|
+
it "is aliased to write" do
|
258
|
+
adapter.read('foo').should be_nil
|
259
|
+
adapter['foo'] = 'bar'
|
260
|
+
adapter.read('foo').should == 'bar'
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe "#[]=" do
|
265
|
+
it "is aliased to write" do
|
266
|
+
adapter.read('foo').should be_nil
|
267
|
+
adapter.set('foo', 'bar')
|
268
|
+
adapter.read('foo').should == 'bar'
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe "#eql?" do
|
273
|
+
it "returns true if same name and client" do
|
274
|
+
adapter.should eql(Adapter[:memory].new({}))
|
275
|
+
end
|
276
|
+
|
277
|
+
it "returns false if different name" do
|
278
|
+
Adapter.define(:hash, valid_module)
|
279
|
+
adapter.should_not eql(Adapter[:hash].new({}))
|
280
|
+
end
|
281
|
+
|
282
|
+
it "returns false if different client" do
|
283
|
+
adapter.should_not eql(Adapter[:memory].new(Object.new))
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe "#==" do
|
288
|
+
it "returns true if same name and client" do
|
289
|
+
adapter.should == Adapter[:memory].new({})
|
290
|
+
end
|
291
|
+
|
292
|
+
it "returns false if different name" do
|
293
|
+
Adapter.define(:hash, valid_module)
|
294
|
+
adapter.should_not == Adapter[:hash].new({})
|
295
|
+
end
|
296
|
+
|
297
|
+
it "returns false if different client" do
|
298
|
+
adapter.should_not == Adapter[:memory].new(Object.new)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
Bundler.require(:default, :development)
|
7
|
+
|
8
|
+
require 'pathname'
|
9
|
+
require 'logger'
|
10
|
+
|
11
|
+
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
12
|
+
lib_path = root_path.join('lib')
|
13
|
+
log_path = root_path.join('log')
|
14
|
+
log_path.mkpath
|
15
|
+
|
16
|
+
require 'support/an_adapter'
|
17
|
+
require 'support/marshal_adapter'
|
18
|
+
require 'support/json_adapter'
|
19
|
+
require 'support/module_helpers'
|
20
|
+
|
21
|
+
logger = Logger.new(log_path.join('test.log'))
|
22
|
+
LogBuddy.init(:logger => logger)
|
23
|
+
|
24
|
+
Rspec.configure do |c|
|
25
|
+
c.include(ModuleHelpers)
|
26
|
+
end
|
27
|
+
|
28
|
+
AdapterTestTypes = {"String" => ["key", "key2"]}
|
data/spec/spec.opts
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
shared_examples_for "an adapter" do
|
2
|
+
it "can read the client" do
|
3
|
+
adapter.client.should == client
|
4
|
+
end
|
5
|
+
|
6
|
+
AdapterTestTypes.each do |type, (key, key2)|
|
7
|
+
it "reads from keys that are #{type}s like a Hash" do
|
8
|
+
handle_failed_connections do
|
9
|
+
adapter[key].should == nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "writes String values to keys that are #{type}s like a Hash" do
|
14
|
+
handle_failed_connections do
|
15
|
+
adapter[key] = "value"
|
16
|
+
adapter[key].should == "value"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "guarantees that a different String value is retrieved from the #{type} key" do
|
21
|
+
handle_failed_connections do
|
22
|
+
value = "value"
|
23
|
+
adapter[key] = value
|
24
|
+
adapter[key].should_not be_equal(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "guarantees that a different Object value is retrieved from the #{type} key" do
|
29
|
+
handle_failed_connections do
|
30
|
+
value = {:foo => :bar}
|
31
|
+
adapter[key] = value
|
32
|
+
adapter[key].should_not be_equal(:foo => :bar)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns false from key? if a #{type} key is not available" do
|
37
|
+
handle_failed_connections do
|
38
|
+
adapter.key?(key).should be_false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns true from key? if a #{type} key is available" do
|
43
|
+
handle_failed_connections do
|
44
|
+
adapter[key] = "value"
|
45
|
+
adapter.key?(key).should be_true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "removes and return an element with a #{type} key from the backing store via delete if it exists" do
|
50
|
+
handle_failed_connections do
|
51
|
+
adapter[key] = "value"
|
52
|
+
adapter.delete(key).should == "value"
|
53
|
+
adapter.key?(key).should be_false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns nil from delete if an element for a #{type} key does not exist" do
|
58
|
+
handle_failed_connections do
|
59
|
+
adapter.delete(key).should be_nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "removes all #{type} keys from the store with clear" do
|
64
|
+
handle_failed_connections do
|
65
|
+
adapter[key] = "value"
|
66
|
+
adapter[key2] = "value2"
|
67
|
+
adapter.clear
|
68
|
+
adapter.key?(key).should_not be_true
|
69
|
+
adapter.key?(key2).should_not be_true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "fetches a #{type} key with a default value with fetch, if the key is not available" do
|
74
|
+
handle_failed_connections do
|
75
|
+
adapter.fetch(key, "value").should == "value"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "fetches a #{type} key with a block with fetch, if the key is not available" do
|
80
|
+
handle_failed_connections do
|
81
|
+
adapter.fetch(key) { |k| "value" }.should == "value"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not run the block if the #{type} key is available" do
|
86
|
+
handle_failed_connections do
|
87
|
+
adapter[key] = "value"
|
88
|
+
unaltered = "unaltered"
|
89
|
+
adapter.fetch(key) { unaltered = "altered" }
|
90
|
+
unaltered.should == "unaltered"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "fetches a #{type} key with a default value with fetch, if the key is available" do
|
95
|
+
handle_failed_connections do
|
96
|
+
adapter[key] = "value2"
|
97
|
+
adapter.fetch(key, "value").should == "value2"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "writes #{key} values with #write" do
|
102
|
+
handle_failed_connections do
|
103
|
+
adapter.write(key, "value")
|
104
|
+
adapter[key].should == "value"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it "refuses to #[] from keys that cannot be marshalled" do
|
110
|
+
handle_failed_connections do
|
111
|
+
lambda do
|
112
|
+
adapter[Struct.new(:foo).new(:bar)]
|
113
|
+
end.should raise_error(TypeError)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it "refuses to fetch from keys that cannot be marshalled" do
|
118
|
+
handle_failed_connections do
|
119
|
+
lambda do
|
120
|
+
adapter.fetch(Struct.new(:foo).new(:bar), true)
|
121
|
+
end.should raise_error(TypeError)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it "refuses to #[]= to keys that cannot be marshalled" do
|
126
|
+
handle_failed_connections do
|
127
|
+
lambda do
|
128
|
+
adapter[Struct.new(:foo).new(:bar)] = "value"
|
129
|
+
end.should raise_error(TypeError)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it "refuses to store to keys that cannot be marshalled" do
|
134
|
+
handle_failed_connections do
|
135
|
+
lambda do
|
136
|
+
adapter.write Struct.new(:foo).new(:bar), "value"
|
137
|
+
end.should raise_error(TypeError)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
it "refuses to check for key? if the key cannot be marshalled" do
|
142
|
+
handle_failed_connections do
|
143
|
+
lambda do
|
144
|
+
adapter.key? Struct.new(:foo).new(:bar)
|
145
|
+
end.should raise_error(TypeError)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it "refuses to delete a key if the key cannot be marshalled" do
|
150
|
+
handle_failed_connections do
|
151
|
+
lambda do
|
152
|
+
adapter.delete Struct.new(:foo).new(:bar)
|
153
|
+
end.should raise_error(TypeError)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it "specifies that it is writable via frozen?" do
|
158
|
+
handle_failed_connections do
|
159
|
+
adapter.should_not be_frozen
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
shared_examples_for "a json adapter" do
|
2
|
+
it_should_behave_like 'an adapter'
|
3
|
+
|
4
|
+
AdapterTestTypes.each do |type, (key, key2)|
|
5
|
+
it "writes Object values to keys that are #{type}s like a Hash" do
|
6
|
+
adapter[key] = {:foo => :bar}
|
7
|
+
adapter[key].should == {'foo' => 'bar'}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
shared_examples_for "a marshaled adapter" do
|
2
|
+
it_should_behave_like 'an adapter'
|
3
|
+
|
4
|
+
AdapterTestTypes.each do |type, (key, key2)|
|
5
|
+
it "writes Object values to keys that are #{type}s like a Hash" do
|
6
|
+
handle_failed_connections do
|
7
|
+
adapter[key] = {:foo => :bar}
|
8
|
+
adapter[key].should == {:foo => :bar}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ModuleHelpers
|
2
|
+
def valid_module
|
3
|
+
Module.new do
|
4
|
+
def read(key)
|
5
|
+
decode(client[key_for(key)])
|
6
|
+
end
|
7
|
+
|
8
|
+
def write(key, value)
|
9
|
+
client[key_for(key)] = encode(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def delete(key)
|
13
|
+
client.delete(key_for(key))
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear
|
17
|
+
client.clear
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle_failed_connections
|
23
|
+
yield
|
24
|
+
rescue => e
|
25
|
+
puts e.inspect
|
26
|
+
puts e.message unless e.message.nil?
|
27
|
+
pending
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 1
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 5
|
9
|
+
version: "0.5"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- John Nunemaker
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-25 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email:
|
23
|
+
- nunemaker@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/adapter/asserts.rb
|
32
|
+
- lib/adapter/defaults.rb
|
33
|
+
- lib/adapter/exceptions.rb
|
34
|
+
- lib/adapter/memory.rb
|
35
|
+
- lib/adapter/version.rb
|
36
|
+
- lib/adapter.rb
|
37
|
+
- spec/adapter/defaults_spec.rb
|
38
|
+
- spec/adapter/memory_spec.rb
|
39
|
+
- spec/adapter_spec.rb
|
40
|
+
- spec/helper.rb
|
41
|
+
- spec/spec.opts
|
42
|
+
- spec/support/an_adapter.rb
|
43
|
+
- spec/support/json_adapter.rb
|
44
|
+
- spec/support/marshal_adapter.rb
|
45
|
+
- spec/support/module_helpers.rb
|
46
|
+
- LICENSE
|
47
|
+
- Rakefile
|
48
|
+
- README.rdoc
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/newtoy/adapter
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.7
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: A simple interface to anything
|
83
|
+
test_files: []
|
84
|
+
|