couch-client 0.0.1
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/CHANGELOG +1 -0
- data/LICENSE +22 -0
- data/Manifest +33 -0
- data/README.markdown +176 -0
- data/Rakefile +17 -0
- data/TODO +11 -0
- data/couch-client.gemspec +33 -0
- data/lib/couch-client.rb +50 -0
- data/lib/couch-client/attachment.rb +32 -0
- data/lib/couch-client/attachment_list.rb +10 -0
- data/lib/couch-client/collection.rb +20 -0
- data/lib/couch-client/connection.rb +104 -0
- data/lib/couch-client/connection_handler.rb +70 -0
- data/lib/couch-client/consistent_hash.rb +98 -0
- data/lib/couch-client/database.rb +39 -0
- data/lib/couch-client/design.rb +79 -0
- data/lib/couch-client/document.rb +151 -0
- data/lib/couch-client/hookup.rb +107 -0
- data/lib/couch-client/row.rb +10 -0
- data/spec/attachment_list_spec.rb +7 -0
- data/spec/attachment_spec.rb +57 -0
- data/spec/collection_spec.rb +43 -0
- data/spec/conection_handler_spec.rb +66 -0
- data/spec/connection_spec.rb +93 -0
- data/spec/consistent_hash_spec.rb +171 -0
- data/spec/couch-client_spec.rb +11 -0
- data/spec/database_spec.rb +44 -0
- data/spec/design_spec.rb +100 -0
- data/spec/document_spec.rb +196 -0
- data/spec/files/image.png +0 -0
- data/spec/files/plain.txt +1 -0
- data/spec/hookup_spec.rb +122 -0
- data/spec/row_spec.rb +35 -0
- data/spec/spec_helper.rb +11 -0
- metadata +130 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
module CouchClient
|
2
|
+
# The Row is an extended Hash that provides additional state to
|
3
|
+
# get status codes and connect documents to the server.
|
4
|
+
class Row < ConsistentHash
|
5
|
+
def initialize(code, row, connection)
|
6
|
+
self.merge!(row)
|
7
|
+
self["doc"] &&= Document.new(code, row["doc"], connection)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
3
|
+
|
4
|
+
describe CouchClient::Attachment do
|
5
|
+
before(:all) do
|
6
|
+
@couch = CouchClient.connect(COUCHDB_TEST_SETTINGS)
|
7
|
+
@couch.database.create
|
8
|
+
|
9
|
+
@alice = @couch.build({"name" => "alice", "city" => "nyc"})
|
10
|
+
@alice.save
|
11
|
+
|
12
|
+
@read = lambda do |file|
|
13
|
+
File.read(File.join(File.dirname(__FILE__), "files", file))
|
14
|
+
end
|
15
|
+
|
16
|
+
@digest = lambda do |file|
|
17
|
+
Digest::SHA1.hexdigest(file)
|
18
|
+
end
|
19
|
+
|
20
|
+
@plain = @read.call("plain.txt")
|
21
|
+
@image = @read.call("image.png")
|
22
|
+
|
23
|
+
@plain_digest = @digest.call(@plain)
|
24
|
+
@image_digest = @digest.call(@image)
|
25
|
+
|
26
|
+
@alice.attach("plain.txt", @plain, "text/plain")
|
27
|
+
@alice.attach("image.png", @image, "image/png")
|
28
|
+
|
29
|
+
@attachment_plain = @alice.saved_doc.attachments["plain.txt"]
|
30
|
+
@attachment_image = @alice.saved_doc.attachments["image.png"]
|
31
|
+
end
|
32
|
+
|
33
|
+
after(:all) do
|
34
|
+
@couch.database.delete!
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#uri' do
|
38
|
+
it 'should yield the uri for the attachment' do
|
39
|
+
@attachment_plain.uri.should eql([@couch.hookup.handler.uri, @alice.id, "plain.txt"].join("/"))
|
40
|
+
@attachment_image.uri.should eql([@couch.hookup.handler.uri, @alice.id, "image.png"].join("/"))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#path' do
|
45
|
+
it 'should yield the uri for the attachment' do
|
46
|
+
@attachment_plain.path.should eql([@couch.hookup.handler.path, @alice.id, "plain.txt"].join("/"))
|
47
|
+
@attachment_image.path.should eql([@couch.hookup.handler.path, @alice.id, "image.png"].join("/"))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#file' do
|
52
|
+
it 'should yield the file for the attachment as a string' do
|
53
|
+
@digest.call(@attachment_plain.data).should eql(@plain_digest)
|
54
|
+
@digest.call(@attachment_image.data).should eql(@image_digest)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe CouchClient::Collection do
|
4
|
+
before(:all) do
|
5
|
+
@couch = CouchClient.connect(COUCHDB_TEST_SETTINGS)
|
6
|
+
@couch.database.create
|
7
|
+
|
8
|
+
factory = lambda do |hash|
|
9
|
+
doc = @couch.build(hash)
|
10
|
+
doc.save
|
11
|
+
doc
|
12
|
+
end
|
13
|
+
|
14
|
+
@alice = factory.call({"_id" => "123", "name" => "alice", "city" => "nyc"})
|
15
|
+
@bob = factory.call({"_id" => "456", "name" => "bob", "city" => "chicago"})
|
16
|
+
@design = factory.call({"_id" => "_design/people",
|
17
|
+
"views" => {
|
18
|
+
"all" => {"map" => "function(doc){emit(doc._id, doc)}"}
|
19
|
+
}
|
20
|
+
})
|
21
|
+
|
22
|
+
@people = @couch.design("people").view("all", "include_docs" => true)
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:all) do
|
26
|
+
@couch.database.delete!
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should be an CouchClient::Collection object' do
|
30
|
+
@people.should be_a_kind_of(CouchClient::Collection)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should have a collection of rows' do
|
34
|
+
@people.size.should eql(2)
|
35
|
+
@people.first["doc"].should eql(@alice)
|
36
|
+
@people.last["doc"].should eql(@bob)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should have `code` and `info` methods' do
|
40
|
+
@people.should respond_to(:code)
|
41
|
+
@people.should respond_to(:info)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe CouchClient::ConnectionHandler do
|
4
|
+
before(:all) do
|
5
|
+
@ch = CouchClient::ConnectionHandler.new
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should have sensible defaults' do
|
9
|
+
@ch.scheme.should eql("http")
|
10
|
+
@ch.host.should eql("localhost")
|
11
|
+
@ch.port.should eql(5984)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should set parameters' do
|
15
|
+
@ch.scheme = "https"
|
16
|
+
@ch.username = "admin"
|
17
|
+
@ch.password = "nimda"
|
18
|
+
@ch.host = "couchone.com"
|
19
|
+
@ch.port = 8080
|
20
|
+
@ch.database = "sandbox"
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should get parameters' do
|
24
|
+
@ch.scheme.should eql("https")
|
25
|
+
@ch.username.should eql("admin")
|
26
|
+
@ch.password.should eql("nimda")
|
27
|
+
@ch.host.should eql("couchone.com")
|
28
|
+
@ch.port.should eql(8080)
|
29
|
+
@ch.database.should eql("sandbox")
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'shoudl output a path string' do
|
33
|
+
@ch.path.should eql("/sandbox")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should output a uri string' do
|
37
|
+
@ch.uri.should eql("https://couchone.com:8080/sandbox")
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should output a uri string with a path if passed a path' do
|
41
|
+
@ch.uri(["path"]).should eql("https://couchone.com:8080/sandbox/path")
|
42
|
+
@ch.uri(["path"], nil).should eql("https://couchone.com:8080/sandbox/path")
|
43
|
+
@ch.uri(["_$,+-/"]).should eql("https://couchone.com:8080/sandbox/_%24%2C%2B-%2F")
|
44
|
+
@ch.uri(["?=&; #"]).should eql("https://couchone.com:8080/sandbox/%3F%3D%26%3B+%23")
|
45
|
+
@ch.uri(["_design/test", "spaces & special/chars"]).should eql("https://couchone.com:8080/sandbox/_design/test/spaces+%26+special%2Fchars")
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should output a uri string with a query if a query is given' do
|
49
|
+
@ch.uri(nil, {"a" => "apple"}).should eql("https://couchone.com:8080/sandbox?a=apple")
|
50
|
+
@ch.uri(nil, {"a" => "apple", "b" => "banana"}).should eql("https://couchone.com:8080/sandbox?a=apple&b=banana")
|
51
|
+
@ch.uri(nil, {"a" => "_$,+-/", "b" => "?=&; #"}).should eql("https://couchone.com:8080/sandbox?a=_%24%2C%2B-%2F&b=%3F%3D%26%3B+%23")
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should output a uri string with path and query if both are given' do
|
55
|
+
@ch.uri(["path", "one", "two"], {"a" => "apple", "b" => "banana"}).should eql("https://couchone.com:8080/sandbox/path/one/two?a=apple&b=banana")
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should properly escape database urls' do
|
59
|
+
@ch.database = "abc123/_$()+-"
|
60
|
+
@ch.uri(["path"]).should eql("https://couchone.com:8080/abc123%2F_%24%28%29%2B-/path")
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should raise an error if an invalid name is given' do
|
64
|
+
lambda{@ch.database = "ABC!@#"}.should raise_error(CouchClient::InvalidDatabaseName)
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe CouchClient::Connection do
|
4
|
+
before(:all) do
|
5
|
+
@couch = CouchClient.connect(COUCHDB_TEST_SETTINGS)
|
6
|
+
@couch.database.create
|
7
|
+
|
8
|
+
factory = lambda do |hash|
|
9
|
+
doc = @couch.build(hash)
|
10
|
+
doc.save
|
11
|
+
doc
|
12
|
+
end
|
13
|
+
|
14
|
+
@alice = factory.call({"name" => "alice", "city" => "nyc"})
|
15
|
+
@bob = factory.call({"name" => "bob", "city" => "chicago"})
|
16
|
+
@deleted = factory.call({"name" => "deleted", "city" => "unknown"})
|
17
|
+
@deleted.delete! # used to test if deleted documents raise an error when fetched
|
18
|
+
@design = factory.call({"_id" => "_design/people", "views" => {"all" => {"map" => "function(doc){emit(doc._id, doc)}"}}})
|
19
|
+
end
|
20
|
+
|
21
|
+
after(:all) do
|
22
|
+
@couch.database.delete!
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should have a Hookup' do
|
26
|
+
@couch.hookup.should be_a(CouchClient::Hookup)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should have a Database' do
|
30
|
+
@couch.database.should be_a(CouchClient::Database)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#[]' do
|
34
|
+
it 'should get the document associated if it exists' do
|
35
|
+
@couch[@alice["_id"]].should eql(@alice)
|
36
|
+
@couch[@bob["_id"]].should eql(@bob)
|
37
|
+
@couch[@design["_id"]].should eql(@design)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should raise an error if the document was deleted' do
|
41
|
+
lambda{@couch[@deleted["_id"]].should}.should raise_error(CouchClient::DocumentNotFound)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should raise an error if the document does not exist' do
|
45
|
+
lambda{@couch['missing'].should}.should raise_error(CouchClient::DocumentNotFound)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should raise an error if the response was not a valid document' do
|
49
|
+
lambda{@couch["_design/people/_view/all"]}.should raise_error(CouchClient::DocumentNotValid)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#design' do
|
54
|
+
it 'should return a design object when a valid design id is given' do
|
55
|
+
@couch.design("people").should be_a(CouchClient::Design)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should allow access to design views' do
|
59
|
+
@couch.design("people").view("all").should be_a(CouchClient::Collection)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#all_docs' do
|
64
|
+
it 'should return a list of all documents stored' do
|
65
|
+
all_docs = @couch.all_docs("include_docs" => true)
|
66
|
+
all_docs.should be_a(CouchClient::Collection)
|
67
|
+
docs = all_docs.map{|doc| doc["doc"].id}
|
68
|
+
docs.should include(@alice.id)
|
69
|
+
docs.should include(@bob.id)
|
70
|
+
docs.should include(@design.id)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '#build' do
|
75
|
+
before(:all) do
|
76
|
+
@charlie = @couch.build({"name" => "charlie", "city" => "san fran"})
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should create a new Document' do
|
80
|
+
@charlie.should be_a(CouchClient::Document)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should have the hash keys and values provided' do
|
84
|
+
@charlie.should eql({"name" => "charlie", "city" => "san fran"})
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#inspect' do
|
89
|
+
it 'should yield an inspect string with valid settings' do
|
90
|
+
@couch.inspect.should eql("#<CouchClient::Connection: uri: http://localhost:5984/couch-client_test>")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe CouchClient::ConsistentHash do
|
4
|
+
describe '#initialize' do
|
5
|
+
describe 'when given a hash' do
|
6
|
+
before do
|
7
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple", "b" => :banana})
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should construct a new ConsistentHash' do
|
11
|
+
@ch[:a].should eql("apple")
|
12
|
+
@ch["a"].should eql("apple")
|
13
|
+
@ch[:b].should eql("banana")
|
14
|
+
@ch["b"].should eql("banana")
|
15
|
+
@ch[:c].should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'when given a default value' do
|
20
|
+
before do
|
21
|
+
@ch = CouchClient::ConsistentHash.new("default")
|
22
|
+
@ch[:a] = "apple"
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should construct a new ConsistentHash with a default' do
|
26
|
+
|
27
|
+
@ch[:a].should eql("apple")
|
28
|
+
@ch[:b].should eql("default")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'when given a hash with a default value' do
|
33
|
+
before do
|
34
|
+
h = Hash.new("default")
|
35
|
+
h[:a] = "apple"
|
36
|
+
@ch = CouchClient::ConsistentHash.new(h)
|
37
|
+
@ch[:b] = "banana"
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should construct a new ConsistentHash with a default' do
|
41
|
+
@ch[:a].should eql("apple")
|
42
|
+
@ch[:b].should eql("banana")
|
43
|
+
@ch[:z].should eql("default")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#default' do
|
49
|
+
before do
|
50
|
+
@ch = CouchClient::ConsistentHash.new("default")
|
51
|
+
@ch[:a] = "apple"
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should give the default value if it does not exists' do
|
55
|
+
@ch.default.should eql("default")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#[]=' do
|
60
|
+
before do
|
61
|
+
@ch = CouchClient::ConsistentHash.new()
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should set a field' do
|
65
|
+
@ch[:a] = "apple"
|
66
|
+
@ch[:a].should eql("apple")
|
67
|
+
@ch["a"].should eql("apple")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#update, #merge!' do
|
72
|
+
before do
|
73
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple", :b => "banana"})
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should update an existing hash with fields from a new hash' do
|
77
|
+
@ch.update({:b => "blueberry", :c => "cherry"})
|
78
|
+
@ch.should eql({"a"=>"apple", "b"=>"blueberry", "c"=>"cherry"})
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#key?' do
|
83
|
+
before do
|
84
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple"})
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should identify if a hash has a key' do
|
88
|
+
@ch.key?(:a).should be_true
|
89
|
+
@ch.key?(:b).should be_false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#fetch' do
|
94
|
+
before do
|
95
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple"})
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should return the value if the key given exists' do
|
99
|
+
@ch.fetch(:a).should eql("apple")
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should raise an error if the key given does not exist' do
|
103
|
+
lambda{@ch.fetch(:b)}.should raise_error(KeyError)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should return a default if one is given' do
|
107
|
+
@ch.fetch(:b, "default").should eql("default")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe '#values_at' do
|
112
|
+
before do
|
113
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple", :b => "banana", :c => "cherry"})
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should return an array of values for the keys given' do
|
117
|
+
@ch.values_at(:a, :c).should eql(["apple", "cherry"])
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#dup' do
|
122
|
+
before do
|
123
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple"})
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should return a new hash' do
|
127
|
+
@ch2 = @ch.dup
|
128
|
+
@ch2.object_id.should_not eql(@ch.object_id)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#merge' do
|
133
|
+
before do
|
134
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple", :b => "banana"})
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should return an new hash with fields from both hashs' do
|
138
|
+
@ch2 = @ch.merge({:b => "blueberry", :c => "cherry"})
|
139
|
+
|
140
|
+
@ch.should eql({"a" => "apple", "b" => "banana"})
|
141
|
+
@ch2.should eql({"a"=>"apple", "b"=>"blueberry", "c"=>"cherry"})
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '#delete' do
|
146
|
+
before do
|
147
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple"})
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should delete the specified field' do
|
151
|
+
@ch.delete(:b)
|
152
|
+
@ch.should eql({"a" => "apple"})
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe '#to_hash' do
|
157
|
+
before do
|
158
|
+
@ch = CouchClient::ConsistentHash.new({:a => "apple"})
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should return a regular hash' do
|
162
|
+
@h = @ch.to_hash
|
163
|
+
|
164
|
+
@ch[:a].should eql("apple")
|
165
|
+
@h[:a].should be_nil
|
166
|
+
|
167
|
+
@ch.should be_an_instance_of(CouchClient::ConsistentHash)
|
168
|
+
@h.should be_an_instance_of(Hash)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|