couch-client 0.0.2 → 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/CHANGELOG +4 -2
- data/README.markdown +87 -13
- data/Rakefile +7 -3
- data/TODO +2 -8
- data/couch-client.gemspec +10 -4
- data/lib/couch-client.rb +2 -4
- data/lib/couch-client/attachment.rb +4 -4
- data/lib/couch-client/attachment_list.rb +1 -1
- data/lib/couch-client/collection.rb +2 -2
- data/lib/couch-client/connection.rb +7 -7
- data/lib/couch-client/connection_handler.rb +6 -1
- data/lib/couch-client/database.rb +1 -1
- data/lib/couch-client/design.rb +52 -17
- data/lib/couch-client/document.rb +8 -8
- data/lib/couch-client/hookup.rb +3 -4
- data/lib/couch-client/row.rb +1 -1
- data/spec/conection_handler_spec.rb +4 -0
- data/spec/connection_spec.rb +11 -0
- data/spec/design_spec.rb +29 -2
- data/spec/spec_helper.rb +20 -0
- metadata +35 -5
data/CHANGELOG
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
-
v0.0.
|
1
|
+
v0.1.0. Adding rake tasks to sync and manage database functions and design documents. Also added support for list and show functions; as well as fulltext administration.
|
2
2
|
|
3
|
-
v0.0.
|
3
|
+
v0.0.2. Fixing gemspec
|
4
|
+
|
5
|
+
v0.0.1. First release
|
data/README.markdown
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Introduction
|
2
2
|
============
|
3
3
|
|
4
|
-
CouchClient is
|
4
|
+
CouchClient is a Ruby interface for CouchDB that provides easy configuration, state management and utility methods.
|
5
5
|
|
6
6
|
Installation
|
7
7
|
------------
|
@@ -50,7 +50,7 @@ Fetching a Document
|
|
50
50
|
person = Couch["a3b556796203eab59c31fa21b00043e3"]
|
51
51
|
|
52
52
|
# You can also pass options if desired
|
53
|
-
person = Couch["a3b556796203eab59c31fa21b00043e3", :
|
53
|
+
person = Couch["a3b556796203eab59c31fa21b00043e3", :attachments => true]
|
54
54
|
|
55
55
|
Getting a Document's id, rev and attachments
|
56
56
|
--------------------------------------------
|
@@ -61,7 +61,7 @@ Getting a Document's id, rev and attachments
|
|
61
61
|
person.rev # => "1-6665e6330ba75e757ce1f6d793305d67"
|
62
62
|
|
63
63
|
# A document's attachments
|
64
|
-
# NOTE: This will be
|
64
|
+
# NOTE: This will be a CouchClient::AttachmentList, and attachments will be CouchClient::Attachment objects
|
65
65
|
person.attachments # => {"plain.txt"=>{"content_type"=>"text/plain", "revpos"=>2, "length"=>406, "stub"=>true}}
|
66
66
|
|
67
67
|
|
@@ -122,10 +122,10 @@ Working with Collections
|
|
122
122
|
# Getting all documents with document fields
|
123
123
|
Couch.all_docs(:include_docs => true)
|
124
124
|
|
125
|
-
# Specifying a `key`, `
|
125
|
+
# Specifying a `key`, `startkey` or `endkey`
|
126
126
|
couch.all_docs(:key => "7f22af967b04d1b88212d3d26b018e89")
|
127
|
-
couch.all_docs(:
|
128
|
-
couch.all_docs(:
|
127
|
+
couch.all_docs(:startkey => 200)
|
128
|
+
couch.all_docs(:endkey => [2010, 01, 01])
|
129
129
|
|
130
130
|
# Getting additional collection information
|
131
131
|
Couch.all_docs.info # => {"total_rows" => 2, "offset" => 0}
|
@@ -140,28 +140,102 @@ Using Design Documents
|
|
140
140
|
# MapReduce Views
|
141
141
|
Couch.design(:people).view(:sum) # => [{"key" => "male", "value" => 1}, {"key" => "female", "value" => 1}]
|
142
142
|
|
143
|
-
Using
|
144
|
-
|
143
|
+
Using Show and List Functions
|
144
|
+
-----------------------------
|
145
|
+
|
146
|
+
# Show Functions
|
147
|
+
Couch.design(:people).show(:html) # => "<h1>alice</h1>"
|
148
|
+
Couch.design(:people).show(:json) # => {"name" => "alice"}
|
149
|
+
|
150
|
+
# List Functions
|
151
|
+
Couch.design(:people).list(:json, :people, :all) # => ["alice", "bob", "charlie"]
|
152
|
+
|
153
|
+
Using FullText Search (Must Have CouchDB-Lucene Installed)
|
154
|
+
----------------------------------------------------------
|
145
155
|
|
146
156
|
# Getting search results
|
147
|
-
Couch.design(:people).fulltext(:by_name, :q => "
|
157
|
+
Couch.design(:people).fulltext(:by_name, :q => "ali*") # => [{"id"=>"a6c92090bbee241e892be1ac4464b9d9", "score"=>4.505526065826416, "fields"=>{"default"=>"alice"}}]
|
148
158
|
|
149
159
|
# Getting additional search results information
|
150
|
-
Couch.design(:people).fulltext(:by_name, :q => "
|
160
|
+
Couch.design(:people).fulltext(:by_name, :q => "ali*").info # => {"q"=>"default:alice", "etag"=>"11e1541e20d9b860", "skip"=>0, "limit"=>25,
|
151
161
|
# "total_rows"=>7, "search_duration"=>0, "fetch_duration"=>1}
|
152
162
|
|
153
163
|
# Getting search index information
|
154
164
|
Couch.design(:people).fulltext(:by_name) # => {"current"=>true, "disk_size"=>3759, "doc_count"=>25, "doc_del_count"=>3, "fields"=>["default"],
|
155
165
|
# "last_modified"=>"1288403429000", "optimized"=>false, "ref_count"=>2}
|
166
|
+
|
167
|
+
# Optimizing an index
|
168
|
+
Couch.design(:people).fulltext(:by_name, :optimize) # => true
|
169
|
+
|
170
|
+
# Expunging an index
|
171
|
+
Couch.design(:people).fulltext(:by_name, :expunge) # => true
|
156
172
|
|
157
|
-
|
158
|
-
|
173
|
+
Convenience Rake Tasks
|
174
|
+
----------------------
|
175
|
+
|
176
|
+
CouchClient rake tasks can be enabled by adding the following to your `Rakefile`.
|
177
|
+
|
178
|
+
CouchClient::RakeTask.new do |c|
|
179
|
+
c.connection = Couch
|
180
|
+
c.design_path = "./designs"
|
181
|
+
end
|
182
|
+
|
183
|
+
Two parameters are available, `connection` should be the actual variable used for your CouchDB interface and `design_path` should be the application's location where design documents will be stored.
|
184
|
+
|
185
|
+
Within the design path, you should format each design document with folders and files corresponding to the fields in your design document.
|
186
|
+
|
187
|
+
designs
|
188
|
+
├── people
|
189
|
+
│ ├── fulltext
|
190
|
+
│ │ └── by_name
|
191
|
+
│ │ └── index.js
|
192
|
+
│ ├── lists
|
193
|
+
│ │ ├── html.js
|
194
|
+
│ │ └── json.js
|
195
|
+
│ ├── shows
|
196
|
+
│ │ ├── html.js
|
197
|
+
│ │ ├── json.js
|
198
|
+
│ │ └── xml.js
|
199
|
+
│ ├── validate_on_update.js
|
200
|
+
│ └── views
|
201
|
+
│ ├── all
|
202
|
+
│ │ └── map.js
|
203
|
+
│ └── sum
|
204
|
+
│ ├── map.js
|
205
|
+
│ └── reduce.js
|
206
|
+
└── robots
|
207
|
+
├── fulltext
|
208
|
+
│ └── by_name
|
209
|
+
│ └── index.js
|
210
|
+
├── validate_on_update.js
|
211
|
+
└── views
|
212
|
+
├── all
|
213
|
+
│ └── map.js
|
214
|
+
└── sum
|
215
|
+
├── map.js
|
216
|
+
└── reduce.js
|
217
|
+
|
218
|
+
Once you have your design documents created, you can rum `rake couch:sync`, and CouchClient will create new documents, update existing documents (only if there are changes) and delete documents that no longer exist.
|
219
|
+
|
220
|
+
CouchClient also offers tasks that help in maintaining CouchDB.
|
221
|
+
|
222
|
+
# Create a database
|
223
|
+
rake couch:create
|
224
|
+
|
225
|
+
# Delete a database
|
226
|
+
rake couch:delete
|
227
|
+
|
228
|
+
# Compact a database
|
229
|
+
rake couch:compact
|
230
|
+
|
231
|
+
Performing Database Administration
|
232
|
+
----------------------------------
|
159
233
|
|
160
234
|
# Create a database
|
161
235
|
Couch.database.create
|
162
236
|
|
163
237
|
# See if a database exists
|
164
|
-
Couch.database.exists
|
238
|
+
Couch.database.exists?
|
165
239
|
|
166
240
|
# Get database stats
|
167
241
|
Couch.database.stats
|
data/Rakefile
CHANGED
@@ -2,7 +2,10 @@ require 'rake'
|
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
require 'echoe'
|
4
4
|
|
5
|
-
|
5
|
+
# Prevent Echoe from running spec tasks, especially as
|
6
|
+
# Spec should be removed in later versions of Rspec 2.
|
7
|
+
Object.send(:remove_const, :Spec)
|
8
|
+
|
6
9
|
RSpec::Core::RakeTask.new do |t|
|
7
10
|
t.rspec_opts = %w[--colour --format progress]
|
8
11
|
end
|
@@ -11,7 +14,8 @@ Echoe.new("couch-client") do |p|
|
|
11
14
|
p.author = "Robert Sosinski"
|
12
15
|
p.email = "email@robertsosinski.com"
|
13
16
|
p.url = "http://github.com/robertsosinski/couch-client"
|
14
|
-
p.description = "
|
15
|
-
p.summary = "
|
17
|
+
p.description = "A Ruby interface for CouchDB"
|
18
|
+
p.summary = "A Ruby interface for CouchDB that provides easy configuration, state management and utility methods."
|
16
19
|
p.runtime_dependencies = ["json >=1.4.6", "curb >=0.7.8"]
|
20
|
+
p.development_dependencies = ["echoe >=4.3.1", "rspec >=2.0.0"]
|
17
21
|
end
|
data/TODO
CHANGED
data/couch-client.gemspec
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{couch-client}
|
5
|
-
s.version = "0.0
|
5
|
+
s.version = "0.1.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Robert Sosinski"]
|
9
|
-
s.date = %q{2010-
|
10
|
-
s.description = %q{
|
9
|
+
s.date = %q{2010-11-05}
|
10
|
+
s.description = %q{A Ruby interface for CouchDB}
|
11
11
|
s.email = %q{email@robertsosinski.com}
|
12
12
|
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.markdown", "TODO", "lib/couch-client.rb", "lib/couch-client/attachment.rb", "lib/couch-client/attachment_list.rb", "lib/couch-client/collection.rb", "lib/couch-client/connection.rb", "lib/couch-client/connection_handler.rb", "lib/couch-client/consistent_hash.rb", "lib/couch-client/database.rb", "lib/couch-client/design.rb", "lib/couch-client/document.rb", "lib/couch-client/hookup.rb", "lib/couch-client/row.rb"]
|
13
13
|
s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.markdown", "Rakefile", "TODO", "lib/couch-client.rb", "lib/couch-client/attachment.rb", "lib/couch-client/attachment_list.rb", "lib/couch-client/collection.rb", "lib/couch-client/connection.rb", "lib/couch-client/connection_handler.rb", "lib/couch-client/consistent_hash.rb", "lib/couch-client/database.rb", "lib/couch-client/design.rb", "lib/couch-client/document.rb", "lib/couch-client/hookup.rb", "lib/couch-client/row.rb", "spec/attachment_list_spec.rb", "spec/attachment_spec.rb", "spec/collection_spec.rb", "spec/conection_handler_spec.rb", "spec/connection_spec.rb", "spec/consistent_hash_spec.rb", "spec/couch-client_spec.rb", "spec/database_spec.rb", "spec/design_spec.rb", "spec/document_spec.rb", "spec/files/image.png", "spec/files/plain.txt", "spec/hookup_spec.rb", "spec/row_spec.rb", "spec/spec_helper.rb", "couch-client.gemspec"]
|
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
s.rubyforge_project = %q{couch-client}
|
18
18
|
s.rubygems_version = %q{1.3.7}
|
19
|
-
s.summary = %q{
|
19
|
+
s.summary = %q{A Ruby interface for CouchDB that provides easy configuration, state management and utility methods.}
|
20
20
|
|
21
21
|
if s.respond_to? :specification_version then
|
22
22
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
@@ -25,12 +25,18 @@ Gem::Specification.new do |s|
|
|
25
25
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
26
26
|
s.add_runtime_dependency(%q<json>, [">= 1.4.6"])
|
27
27
|
s.add_runtime_dependency(%q<curb>, [">= 0.7.8"])
|
28
|
+
s.add_development_dependency(%q<echoe>, [">= 4.3.1"])
|
29
|
+
s.add_development_dependency(%q<rspec>, [">= 2.0.0"])
|
28
30
|
else
|
29
31
|
s.add_dependency(%q<json>, [">= 1.4.6"])
|
30
32
|
s.add_dependency(%q<curb>, [">= 0.7.8"])
|
33
|
+
s.add_dependency(%q<echoe>, [">= 4.3.1"])
|
34
|
+
s.add_dependency(%q<rspec>, [">= 2.0.0"])
|
31
35
|
end
|
32
36
|
else
|
33
37
|
s.add_dependency(%q<json>, [">= 1.4.6"])
|
34
38
|
s.add_dependency(%q<curb>, [">= 0.7.8"])
|
39
|
+
s.add_dependency(%q<echoe>, [">= 4.3.1"])
|
40
|
+
s.add_dependency(%q<rspec>, [">= 2.0.0"])
|
35
41
|
end
|
36
42
|
end
|
data/lib/couch-client.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
$:.unshift(File.dirname(File.expand_path(__FILE__)))
|
2
2
|
|
3
|
-
# require '../core_ext/hash'
|
4
|
-
|
5
3
|
require 'couch-client/consistent_hash'
|
6
4
|
require 'couch-client/connection'
|
7
5
|
require 'couch-client/connection_handler'
|
@@ -13,11 +11,11 @@ require 'couch-client/attachment'
|
|
13
11
|
require 'couch-client/design'
|
14
12
|
require 'couch-client/collection'
|
15
13
|
require 'couch-client/row'
|
16
|
-
require
|
14
|
+
require "couch-client/rake_task" if defined?(Rake)
|
17
15
|
|
18
16
|
# The CouchClient module is the overall container of all CouchClient logic.
|
19
17
|
module CouchClient
|
20
|
-
VERSION = "0.0
|
18
|
+
VERSION = "0.1.0"
|
21
19
|
|
22
20
|
class Error < Exception; end
|
23
21
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module CouchClient
|
2
|
-
#
|
3
|
-
# interact with attached files saved within a document.
|
2
|
+
# Attachment is an extended Hash that provides additional methods
|
3
|
+
# to interact with attached files saved within a document.
|
4
4
|
class Attachment < ConsistentHash
|
5
5
|
attr_reader :name
|
6
6
|
|
7
|
-
# Attachment is constructed the id of the document it is attached to,
|
7
|
+
# Attachment is constructed with the id of the document it is attached to,
|
8
8
|
# the filename, file stub and connection object.
|
9
9
|
def initialize(id, name, stub, connection)
|
10
10
|
self.merge!(stub)
|
@@ -26,7 +26,7 @@ module CouchClient
|
|
26
26
|
|
27
27
|
# Returns a string that contains attachment data
|
28
28
|
def data
|
29
|
-
@connection.hookup.get([@id, @name], nil, self["content_type"]).last
|
29
|
+
@data ||= @connection.hookup.get([@id, @name], nil, self["content_type"]).last
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module CouchClient
|
2
|
-
#
|
2
|
+
# AttachmentList prevents ConsistentHash from absorbing
|
3
3
|
# instances of Attachment and making them a ConsistentHash.
|
4
4
|
class AttachmentList < ConsistentHash
|
5
5
|
# AttachmentList is constructed with a hash of attachments.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module CouchClient
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# Collection is an extended Array that provides additional methods and
|
3
|
+
# state to get status codes, info and connect documents to the server.
|
4
4
|
class Collection < Array
|
5
5
|
attr_reader :code, :info
|
6
6
|
|
@@ -3,7 +3,7 @@ module CouchClient
|
|
3
3
|
class DocumentNotValid < Exception; end
|
4
4
|
class DocumentNotFound < Exception; end
|
5
5
|
|
6
|
-
#
|
6
|
+
# Connection is the high-level interface used to interact with the CouchDB Server.
|
7
7
|
class Connection
|
8
8
|
attr_reader :hookup, :database
|
9
9
|
|
@@ -75,16 +75,16 @@ module CouchClient
|
|
75
75
|
|
76
76
|
# Acts as the interface to CouchDB's `_all_docs` map view.
|
77
77
|
def all_docs(options = {})
|
78
|
-
# key, startkey and endkey must be JSON encoded
|
79
|
-
["key", "startkey", "endkey"].each do |key|
|
80
|
-
options[key] &&= options[key].to_json
|
81
|
-
end
|
82
|
-
|
83
78
|
# Create a new Collection with the response code, body and connection.
|
84
79
|
Collection.new(*@hookup.get(["_all_docs"], options), self)
|
85
80
|
end
|
86
81
|
|
87
|
-
#
|
82
|
+
# Returns a list of all _design documents.
|
83
|
+
def all_design_docs(options = {})
|
84
|
+
all_docs({"startkey" => "_design/", "endkey" => "_design0"}.merge(options))
|
85
|
+
end
|
86
|
+
|
87
|
+
# The interface used to construct new CouchDB documents. Once constructed,
|
88
88
|
# these documents can be saved, updated, validated and deleted.
|
89
89
|
def build(body = {})
|
90
90
|
Document.new(nil, body, self)
|
@@ -5,7 +5,7 @@ module CouchClient
|
|
5
5
|
class InvalidQueryObject < Exception; end
|
6
6
|
class InvalidDatabaseName < Exception; end
|
7
7
|
|
8
|
-
#
|
8
|
+
# ConnectionHandler creates properly formed URIs and paths, while also
|
9
9
|
# specifying sensible defaults for CouchDB. Once initialized, parameters
|
10
10
|
# can be wrote and read using getter and setter syntax.
|
11
11
|
class ConnectionHandler
|
@@ -50,6 +50,11 @@ module CouchClient
|
|
50
50
|
raise InvalidPathObject.new("path must be of type 'Array' not of type '#{path_obj.class}'")
|
51
51
|
end
|
52
52
|
|
53
|
+
# key, startkey and endkey must be JSON encoded to be valid.
|
54
|
+
["key", :key, "startkey", :startkey, "endkey", :endkey].each do |key|
|
55
|
+
query_obj[key] &&= query_obj[key].to_json
|
56
|
+
end
|
57
|
+
|
53
58
|
query_str = if query_obj.is_a?(Hash)
|
54
59
|
# If a Hash, stringify and escape each object, join each key/value with a "=" and each pair with a "&"
|
55
60
|
query_obj.to_a.map{|q| q.map{|r| CGI.escape(r.to_s)}.join("=")}.join("&")
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module CouchClient
|
2
|
-
#
|
2
|
+
# Database is just an organized collection of functions that interact with the
|
3
3
|
# CouchDB database such as stats, creation, compaction, replication and deletion.
|
4
4
|
class Database
|
5
5
|
# Database is constructed with a connection that is used to make HTTP requests to the server.
|
data/lib/couch-client/design.rb
CHANGED
@@ -3,8 +3,9 @@ module CouchClient
|
|
3
3
|
class ShowNotFound < Exception; end
|
4
4
|
class ListNotFound < Exception; end
|
5
5
|
class FullTextNotFound < Exception; end
|
6
|
+
class FullTextRequestBad < Exception; end
|
6
7
|
|
7
|
-
#
|
8
|
+
# Design is the interface used to interact with design documents
|
8
9
|
# in order make view, show, list and fulltext requests.
|
9
10
|
class Design
|
10
11
|
attr_accessor :id
|
@@ -18,11 +19,6 @@ module CouchClient
|
|
18
19
|
|
19
20
|
# Makes requests to the server that return mappped/reduced view collections.
|
20
21
|
def view(name, options = {})
|
21
|
-
# key, startkey and endkey must be JSON encoded
|
22
|
-
["key", "startkey", "endkey"].each do |key|
|
23
|
-
options[key] &&= options[key].to_json
|
24
|
-
end
|
25
|
-
|
26
22
|
code, body = @connection.hookup.get(["_design", id, "_view", name], options)
|
27
23
|
|
28
24
|
case code
|
@@ -37,34 +33,73 @@ module CouchClient
|
|
37
33
|
raise Error.new("code: #{code}, error: #{body["error"]}, reason: #{body["reason"]}")
|
38
34
|
end
|
39
35
|
end
|
36
|
+
|
37
|
+
# Makes requests to the server that return show objects.
|
38
|
+
def show(name, document_id, options = {})
|
39
|
+
code, body = @connection.hookup.get(["_design", id, "_show", name, document_id], options, nil)
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
case code
|
42
|
+
when 200
|
43
|
+
body.is_a?(Hash) ? ConsistentHash.new(body) : body
|
44
|
+
when 404
|
45
|
+
# Raise an error if nothing was found
|
46
|
+
raise ViewNotFound.new("could not find show field '#{name}' for design '#{id}'")
|
47
|
+
else
|
48
|
+
# Also raise an error if something else happens
|
49
|
+
raise Error.new("code: #{code}, error: #{body["error"]}, reason: #{body["reason"]}")
|
50
|
+
end
|
44
51
|
end
|
52
|
+
|
53
|
+
# Makes requests to the server that list objects.
|
54
|
+
def list(name, document_id, view_name, options = {})
|
55
|
+
code, body = @connection.hookup.get(["_design", id, "_list", name, document_id, view_name], options)
|
45
56
|
|
46
|
-
|
47
|
-
|
48
|
-
|
57
|
+
case code
|
58
|
+
when 200
|
59
|
+
body.is_a?(Hash) ? ConsistentHash.new(body) : body
|
60
|
+
when 404
|
61
|
+
# Raise an error if nothing was found
|
62
|
+
raise ViewNotFound.new("could not find list field '#{name}' for design '#{id}'")
|
63
|
+
else
|
64
|
+
# Also raise an error if something else happens
|
65
|
+
raise Error.new("code: #{code}, error: #{body["error"]}, reason: #{body["reason"]}")
|
66
|
+
end
|
49
67
|
end
|
50
68
|
|
51
69
|
# Makes requests to the server that return lucene search results.
|
52
70
|
def fulltext(name, options = {})
|
53
|
-
|
71
|
+
path = ["_fti", "_design", id, name]
|
72
|
+
verb = :get
|
73
|
+
|
74
|
+
# Options may be a Hash or a String. Hashes are used for fulltext queries,
|
75
|
+
# while String are used for administration operations (such as optimizing).
|
76
|
+
if [String, Symbol].include?(options.class)
|
77
|
+
path << "_#{options}"
|
78
|
+
verb = :post
|
79
|
+
options = {}
|
80
|
+
end
|
81
|
+
|
82
|
+
code, body = @connection.hookup.send(verb, path, options)
|
54
83
|
|
55
84
|
case code
|
56
85
|
when 200
|
57
86
|
if body["rows"]
|
58
|
-
# Return a serch result if a query was provided
|
87
|
+
# Return a serch result if a query was provided.
|
59
88
|
Collection.new(code, body, self)
|
60
89
|
else
|
61
|
-
# Return a status hash if a query was not provided
|
90
|
+
# Return a status hash if a query was not provided.
|
62
91
|
body
|
63
92
|
end
|
93
|
+
when 202
|
94
|
+
true # Return true when administration operations are successfully performed.
|
64
95
|
else
|
65
|
-
|
66
|
-
|
96
|
+
case body["reason"]
|
97
|
+
when "no_such_view"
|
98
|
+
# Raise an error if a fulltext function was not found.
|
67
99
|
raise FullTextNotFound.new("could not find fulltext field '#{name}' for design '#{id}'")
|
100
|
+
when "bad_request"
|
101
|
+
# Raise an error if a request was not formated properly (i.e. is bad).
|
102
|
+
raise FullTextRequestBad.new("bad request made for fulltext field '#{name}' for design '#{id}'")
|
68
103
|
else
|
69
104
|
# Also raise an error if something else happens
|
70
105
|
raise Error.new("code: #{code}, error: #{body["error"]}, reason: #{body["reason"]}")
|
@@ -3,7 +3,7 @@ module CouchClient
|
|
3
3
|
class AttachmentError < Exception; end
|
4
4
|
class DocumentNotAvailable < Exception; end
|
5
5
|
|
6
|
-
#
|
6
|
+
# Document is an extended Hash that provides additional methods to
|
7
7
|
# save, update (with attachments), and delete documents on the CouchDB.
|
8
8
|
class Document < ConsistentHash
|
9
9
|
attr_reader :code, :error
|
@@ -42,14 +42,14 @@ module CouchClient
|
|
42
42
|
# Returns a copy of the same document that is currently saved on the server.
|
43
43
|
def saved_doc(query = {})
|
44
44
|
if new?
|
45
|
-
raise DocumentNotAvailable.new(
|
45
|
+
raise DocumentNotAvailable.new("this document is new and therefore has not been saved yet")
|
46
46
|
else
|
47
47
|
@connection[self.id, query]
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
# Tries to save the document to the server. If it us unable to,
|
52
|
-
# it will save the error and make it available
|
52
|
+
# it will save the error and make it available via #error.
|
53
53
|
def save
|
54
54
|
# Ensure that "_id" is a String if it is defined.
|
55
55
|
if self.key?("_id") && !self["_id"].is_a?(String)
|
@@ -71,14 +71,14 @@ module CouchClient
|
|
71
71
|
@deleted = false
|
72
72
|
true
|
73
73
|
else
|
74
|
-
#
|
74
|
+
# Else save error message and return `false`.
|
75
75
|
@error = {body["error"] => body["reason"]}
|
76
76
|
false
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
80
|
# Tries to attach a file to the document. If it us unable to,
|
81
|
-
# it will save the error and make it available
|
81
|
+
# it will save the error and make it available via #error.
|
82
82
|
def attach(name, content, content_type)
|
83
83
|
# The document must already be saved to the server before a file can be attached.
|
84
84
|
if self.rev
|
@@ -90,7 +90,7 @@ module CouchClient
|
|
90
90
|
self.rev = body["rev"]
|
91
91
|
true
|
92
92
|
else
|
93
|
-
#
|
93
|
+
# Else save error message and return `false`.
|
94
94
|
@error = {body["error"] => body["reason"]}
|
95
95
|
false
|
96
96
|
end
|
@@ -101,7 +101,7 @@ module CouchClient
|
|
101
101
|
end
|
102
102
|
|
103
103
|
# Tries to delete a file from the server. If it us unable to,
|
104
|
-
# it will save the error and make it available
|
104
|
+
# it will save the error and make it available via #error.
|
105
105
|
def delete!
|
106
106
|
@code, body = @connection.hookup.delete([id], {"rev" => rev})
|
107
107
|
|
@@ -112,7 +112,7 @@ module CouchClient
|
|
112
112
|
@deleted = true
|
113
113
|
true
|
114
114
|
else
|
115
|
-
#
|
115
|
+
# Else save error message and return `false`.
|
116
116
|
@error = {body["error"] => body["reason"]}
|
117
117
|
false
|
118
118
|
end
|
data/lib/couch-client/hookup.rb
CHANGED
@@ -6,7 +6,7 @@ module CouchClient
|
|
6
6
|
class InvalidJSONData < Exception; end
|
7
7
|
class SymbolUsedInField < Exception; end
|
8
8
|
|
9
|
-
#
|
9
|
+
# Hookup is the basic HTTP interface that connects CouchClient to CouchDB.
|
10
10
|
# Hookup can use any HTTP library if the conventions listed below are followed.
|
11
11
|
#
|
12
12
|
# If modified, Hookup must have head, get, post, put and delete instance methods.
|
@@ -66,8 +66,7 @@ module CouchClient
|
|
66
66
|
# Setup curb options block
|
67
67
|
options = lambda do |easy|
|
68
68
|
easy.headers["User-Agent"] = "couch-client v#{VERSION}"
|
69
|
-
easy.headers["Content-Type"] = content_type if content_type
|
70
|
-
easy.headers["Accepts"] = content_type if content_type
|
69
|
+
easy.headers["Content-Type"] = content_type if content_type && [:post, :put].include?(verb)
|
71
70
|
easy.username = handler.username
|
72
71
|
easy.userpwd = handler.password
|
73
72
|
end
|
@@ -91,7 +90,7 @@ module CouchClient
|
|
91
90
|
# body is either a nil, a hash or a string containing attachment data
|
92
91
|
body = if easy.body_str == "" || easy.body_str.nil?
|
93
92
|
nil
|
94
|
-
elsif content_type
|
93
|
+
elsif [content_type, easy.content_type].include?("application/json") || [:post, :put, :delete].include?(verb)
|
95
94
|
begin
|
96
95
|
JSON.parse(easy.body_str)
|
97
96
|
rescue
|
data/lib/couch-client/row.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module CouchClient
|
2
|
-
#
|
2
|
+
# Row is an extended Hash that provides additional state to
|
3
3
|
# get status codes and connect documents to the server.
|
4
4
|
class Row < ConsistentHash
|
5
5
|
def initialize(code, row, connection)
|
@@ -60,6 +60,10 @@ describe CouchClient::ConnectionHandler do
|
|
60
60
|
@ch.uri(["path"]).should eql("https://couchone.com:8080/abc123%2F_%24%28%29%2B-/path")
|
61
61
|
end
|
62
62
|
|
63
|
+
it 'should properly json encode key, startkey and endkey query paramenters' do
|
64
|
+
@ch.uri(["path"], {:key => "7f22af967b04d1b88212d3d26b018e89", "startkey" => 2010, :endkey => [2010, 01, 01]}).should eql("https://couchone.com:8080/abc123%2F_%24%28%29%2B-/path?key=%227f22af967b04d1b88212d3d26b018e89%22&startkey=2010&endkey=%5B2010%2C1%2C1%5D")
|
65
|
+
end
|
66
|
+
|
63
67
|
it 'should raise an error if an invalid name is given' do
|
64
68
|
lambda{@ch.database = "ABC!@#"}.should raise_error(CouchClient::InvalidDatabaseName)
|
65
69
|
end
|
data/spec/connection_spec.rb
CHANGED
@@ -71,6 +71,17 @@ describe CouchClient::Connection do
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
describe '#all_design_docs' do
|
75
|
+
it 'should return a list of all documents stored' do
|
76
|
+
all_docs = @couch.all_design_docs("include_docs" => true)
|
77
|
+
all_docs.should be_a(CouchClient::Collection)
|
78
|
+
docs = all_docs.map{|doc| doc["doc"].id}
|
79
|
+
docs.should_not include(@alice.id)
|
80
|
+
docs.should_not include(@bob.id)
|
81
|
+
docs.should include(@design.id)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
74
85
|
describe '#build' do
|
75
86
|
before(:all) do
|
76
87
|
@charlie = @couch.build({"name" => "charlie", "city" => "san fran"})
|
data/spec/design_spec.rb
CHANGED
@@ -19,6 +19,13 @@ describe CouchClient::Connection do
|
|
19
19
|
"all" => {"map" => "function(doc){emit(doc._id, doc)}"},
|
20
20
|
"sum" => {"map" => "function(doc){emit(null, 1)}", "reduce" => "function(id, values, rereduce){return sum(values)}"},
|
21
21
|
},
|
22
|
+
"shows" => {
|
23
|
+
"html" => "function(doc, req){return{body: '<h1>' + doc.name + '</h1>', headers: {'Content-Type': 'text/html'}}}",
|
24
|
+
"json" => "function(doc, req){return{body: JSON.stringify({'name': doc.name}), headers: {'Content-Type': 'application/json'}}}"
|
25
|
+
},
|
26
|
+
"lists" => {
|
27
|
+
"json" => "function(head, req){var row;var rows = [];while(row = getRow()){rows.push(row.value.name);}send(JSON.stringify(rows));}"
|
28
|
+
},
|
22
29
|
"fulltext" => {
|
23
30
|
"by_name" => {
|
24
31
|
"index" => "function(doc){var ret = new Document();ret.add(doc.name);return ret;}"
|
@@ -78,11 +85,19 @@ describe CouchClient::Connection do
|
|
78
85
|
end
|
79
86
|
|
80
87
|
describe '#show' do
|
81
|
-
|
88
|
+
it 'should return valid html for a html show function' do
|
89
|
+
@people.show("html", "123").should eql("<h1>alice</h1>")
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should return valid json for a json show function' do
|
93
|
+
@people.show("json", "123").should eql({"name"=>"alice"})
|
94
|
+
end
|
82
95
|
end
|
83
96
|
|
84
97
|
describe '#list' do
|
85
|
-
|
98
|
+
it 'should return valid json for a json list function' do
|
99
|
+
@people.list("json", "people", "all").should eql(["alice", "bob", "charlie"])
|
100
|
+
end
|
86
101
|
end
|
87
102
|
|
88
103
|
describe '#fulltext' do
|
@@ -96,5 +111,17 @@ describe CouchClient::Connection do
|
|
96
111
|
fulltext.info.should be_a(Hash)
|
97
112
|
fulltext.first["id"].should eql(@alice.id)
|
98
113
|
end
|
114
|
+
|
115
|
+
it 'should return true when administration operations are successfully performed' do
|
116
|
+
@people.fulltext("by_name", "optimize").should be_true
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should raise a "not found" error if the fulltext field was not found' do
|
120
|
+
lambda{@people.fulltext("not_found")}.should raise_error(CouchClient::FullTextNotFound)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should raise a "bad request" error if the requested administration operation was not valid' do
|
124
|
+
lambda{@people.fulltext("by_name", "bad_request")}.should raise_error(CouchClient::FullTextRequestBad)
|
125
|
+
end
|
99
126
|
end
|
100
127
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'tempfile'
|
2
3
|
require 'rspec'
|
4
|
+
require 'rake'
|
3
5
|
|
4
6
|
Rspec.configure do |c|
|
5
7
|
c.mock_with :rspec
|
@@ -8,4 +10,22 @@ end
|
|
8
10
|
COUCHDB_TEST_SETTINGS = {:database => "couch-client_test"}
|
9
11
|
COUCHDB_TEST_DATABASE = COUCHDB_TEST_SETTINGS[:database]
|
10
12
|
|
13
|
+
def suppress
|
14
|
+
temp_f = Tempfile.new("suppress")
|
15
|
+
save_stdout = $stdout.dup
|
16
|
+
save_stderr = $stderr.dup
|
17
|
+
begin
|
18
|
+
$stdout.reopen(temp_f)
|
19
|
+
$stderr.reopen(temp_f)
|
20
|
+
yield
|
21
|
+
rescue
|
22
|
+
temp_f.flush
|
23
|
+
save_stdout.puts File.open(temp_f.path).read
|
24
|
+
raise
|
25
|
+
ensure
|
26
|
+
$stdout.reopen(save_stdout)
|
27
|
+
$stderr.reopen(save_stderr)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
11
31
|
require File.join(File.dirname(File.expand_path(__FILE__)), "..", "lib", "couch-client")
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 1
|
7
8
|
- 0
|
8
|
-
|
9
|
-
version: 0.0.2
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Robert Sosinski
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-11-05 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -47,7 +47,37 @@ dependencies:
|
|
47
47
|
version: 0.7.8
|
48
48
|
type: :runtime
|
49
49
|
version_requirements: *id002
|
50
|
-
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: echoe
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 4
|
60
|
+
- 3
|
61
|
+
- 1
|
62
|
+
version: 4.3.1
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: rspec
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 2
|
75
|
+
- 0
|
76
|
+
- 0
|
77
|
+
version: 2.0.0
|
78
|
+
type: :development
|
79
|
+
version_requirements: *id004
|
80
|
+
description: A Ruby interface for CouchDB
|
51
81
|
email: email@robertsosinski.com
|
52
82
|
executables: []
|
53
83
|
|
@@ -142,6 +172,6 @@ rubyforge_project: couch-client
|
|
142
172
|
rubygems_version: 1.3.7
|
143
173
|
signing_key:
|
144
174
|
specification_version: 3
|
145
|
-
summary:
|
175
|
+
summary: A Ruby interface for CouchDB that provides easy configuration, state management and utility methods.
|
146
176
|
test_files: []
|
147
177
|
|