couchrest 1.2.1 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.travis.yml +1 -0
- data/README.md +11 -7
- data/VERSION +1 -1
- data/couchrest.gemspec +7 -6
- data/history.txt +8 -0
- data/lib/couchrest.rb +26 -31
- data/lib/couchrest/connection.rb +251 -0
- data/lib/couchrest/database.rb +75 -79
- data/lib/couchrest/design.rb +1 -1
- data/lib/couchrest/exceptions.rb +108 -0
- data/lib/couchrest/helper/pager.rb +3 -1
- data/lib/couchrest/helper/stream_row_parser.rb +93 -0
- data/lib/couchrest/rest_api.rb +33 -134
- data/lib/couchrest/server.rb +34 -47
- data/spec/couchrest/connection_spec.rb +415 -0
- data/spec/couchrest/couchrest_spec.rb +61 -67
- data/spec/couchrest/database_spec.rb +151 -147
- data/spec/couchrest/design_spec.rb +28 -28
- data/spec/couchrest/document_spec.rb +72 -70
- data/spec/couchrest/exceptions_spec.rb +74 -0
- data/spec/couchrest/helpers/pager_spec.rb +22 -22
- data/spec/couchrest/helpers/stream_row_parser_spec.rb +154 -0
- data/spec/couchrest/rest_api_spec.rb +44 -208
- data/spec/couchrest/server_spec.rb +0 -29
- data/spec/spec_helper.rb +11 -6
- metadata +31 -17
- data/lib/couchrest/helper/streamer.rb +0 -63
- data/lib/couchrest/monkeypatches.rb +0 -25
- data/spec/couchrest/helpers/streamer_spec.rb +0 -134
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 14f12656b8e131a2aaea47e2556e4a6baa80122f
|
4
|
+
data.tar.gz: a2cc1b7c8d35eb35ffd0b9ef10083678c3c186f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7cada5e2ef0dc7d6f362bacc12a89c02c0d96494ee86dc2eebf443dc02a8967443e296b3759f797a1dffad5bc9d4c1efe0fdfd7c8ad01f8a3a413a0fe01aee8b
|
7
|
+
data.tar.gz: 4dc2fee7d91c8927a008a91aa41ec561127ee681221489953627edec08f6bc1d120135e87d6f933dc6f8201aebadafc45df26a92aecf95a6ffda4e7188e1d158
|
data/.rspec
ADDED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,16 +1,15 @@
|
|
1
|
-
# CouchRest: CouchDB, close to the metal
|
1
|
+
# CouchRest: CouchDB, close to the metal
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
CouchDB's HTTP API, managing JSON serialization, and remembering the URI-paths
|
3
|
+
[![Build Status](https://travis-ci.org/couchrest/couchrest.png)](https://travis-ci.org/couchrest/couchrest)
|
4
|
+
|
5
|
+
CouchRest wraps CouchDB's HTTP API using persistent connections with the [HTTPClient gem](https://github.com/nahi/httpclient), managing JSON serialization, and remembering the URI-paths
|
7
6
|
to CouchDB's API endpoints so you don't have to.
|
8
7
|
|
9
8
|
CouchRest is designed to make a simple base for application and framework-specific object oriented APIs. CouchRest is Object-Mapper agnostic, the parsed JSON it returns from CouchDB shows up as subclasses of Ruby's Hash. Naked JSON, just as it was mean to be.
|
10
9
|
|
11
10
|
## CouchDB Version
|
12
11
|
|
13
|
-
Tested on latest stable release (1.6.X), but
|
12
|
+
Tested on latest stable release (1.6.X), but should work on older versions above 1.0. Also known to work on [Cloudant](http://cloudant.com).
|
14
13
|
|
15
14
|
## Install
|
16
15
|
|
@@ -32,10 +31,15 @@ the dependencies and then run the tests:
|
|
32
31
|
To date, the couchrest specs have been shown to run on:
|
33
32
|
|
34
33
|
* MRI Ruby 1.9.3 and later
|
35
|
-
* JRuby
|
34
|
+
* JRuby 1.7.19
|
35
|
+
* Rubinius 2.5.7
|
36
|
+
|
37
|
+
See the [Travis Build status](https://travis-ci.org/couchrest/couchrest) for more details.
|
36
38
|
|
37
39
|
## Docs
|
38
40
|
|
41
|
+
Changes history: [history.txt](./history.txt)
|
42
|
+
|
39
43
|
API: [http://rdoc.info/projects/couchrest/couchrest](http://rdoc.info/projects/couchrest/couchrest)
|
40
44
|
|
41
45
|
Check the wiki for documentation and examples [http://wiki.github.com/couchrest/couchrest](http://wiki.github.com/couchrest/couchrest)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0.beta1
|
data/couchrest.gemspec
CHANGED
@@ -27,10 +27,11 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.rubygems_version = %q{1.3.7}
|
28
28
|
s.summary = %q{Lean and RESTful interface to CouchDB.}
|
29
29
|
|
30
|
-
s.add_dependency(
|
31
|
-
s.add_dependency(
|
32
|
-
s.add_dependency(
|
33
|
-
s.add_development_dependency(
|
34
|
-
s.add_development_dependency(
|
35
|
-
s.add_development_dependency(
|
30
|
+
s.add_dependency("httpclient", ["~> 2.6.0"])
|
31
|
+
s.add_dependency("mime-types", [">= 1.15"])
|
32
|
+
s.add_dependency("multi_json", ["~> 1.7"])
|
33
|
+
s.add_development_dependency("json", [">= 1.7.0"])
|
34
|
+
s.add_development_dependency("rspec", "~> 2.14.1")
|
35
|
+
s.add_development_dependency("rake")
|
36
|
+
s.add_development_dependency("webmock")
|
36
37
|
end
|
data/history.txt
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
== 2.0.0.beta1
|
2
|
+
|
3
|
+
* Major Changes
|
4
|
+
* Refactoring to support persisitent HTTP connections with HTTPClient gem (@samlown)
|
5
|
+
* Using Ruby's URI object consistently. (@samlown)
|
6
|
+
* Removing concepts of `Server#available_databases` and `Server#default_databases` (@samlown)
|
7
|
+
* Removing the `Streamer` class, added support for built-in streamer (@samlown)
|
8
|
+
|
1
9
|
== 1.2.1 - 2015-06-25
|
2
10
|
|
3
11
|
* Changes
|
data/lib/couchrest.rb
CHANGED
@@ -12,32 +12,34 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
require 'rest_client'
|
16
15
|
require 'multi_json'
|
16
|
+
require 'mime/types'
|
17
|
+
require 'httpclient'
|
17
18
|
|
18
|
-
# Not sure why this is required, so removed until a reason is found!
|
19
19
|
$:.unshift File.dirname(__FILE__) unless
|
20
20
|
$:.include?(File.dirname(__FILE__)) ||
|
21
21
|
$:.include?(File.expand_path(File.dirname(__FILE__)))
|
22
22
|
|
23
|
-
require 'couchrest/
|
23
|
+
require 'couchrest/exceptions'
|
24
|
+
require 'couchrest/connection'
|
24
25
|
require 'couchrest/rest_api'
|
25
26
|
require 'couchrest/support/inheritable_attributes'
|
26
27
|
|
27
28
|
require 'forwardable'
|
29
|
+
require 'tempfile'
|
28
30
|
|
29
31
|
# = CouchDB, close to the metal
|
30
32
|
module CouchRest
|
31
|
-
autoload :Attributes,
|
32
|
-
autoload :Server,
|
33
|
-
autoload :Database,
|
34
|
-
autoload :Document,
|
35
|
-
autoload :Design,
|
36
|
-
autoload :Model,
|
37
|
-
autoload :Pager,
|
38
|
-
autoload :
|
39
|
-
autoload :
|
40
|
-
autoload :Upgrade,
|
33
|
+
autoload :Attributes, 'couchrest/attributes'
|
34
|
+
autoload :Server, 'couchrest/server'
|
35
|
+
autoload :Database, 'couchrest/database'
|
36
|
+
autoload :Document, 'couchrest/document'
|
37
|
+
autoload :Design, 'couchrest/design'
|
38
|
+
autoload :Model, 'couchrest/model'
|
39
|
+
autoload :Pager, 'couchrest/helper/pager'
|
40
|
+
autoload :Attachments, 'couchrest/helper/attachments'
|
41
|
+
autoload :StreamRowParser, 'couchrest/helper/stream_row_parser'
|
42
|
+
autoload :Upgrade, 'couchrest/helper/upgrade'
|
41
43
|
|
42
44
|
# we extend CouchRest with the RestAPI module which gives us acess to
|
43
45
|
# the get, post, put, delete and copy
|
@@ -48,8 +50,7 @@ module CouchRest
|
|
48
50
|
# some helpers for tasks like instantiating a new Database or Server instance.
|
49
51
|
class << self
|
50
52
|
|
51
|
-
#
|
52
|
-
# depending on the specificity.
|
53
|
+
# Instantiate a new Server object
|
53
54
|
def new(*opts)
|
54
55
|
Server.new(*opts)
|
55
56
|
end
|
@@ -88,9 +89,9 @@ module CouchRest
|
|
88
89
|
}
|
89
90
|
end
|
90
91
|
|
91
|
-
#
|
92
|
+
# Set default proxy to use in connections
|
92
93
|
def proxy url
|
93
|
-
|
94
|
+
CouchRest::Connection.proxy = url
|
94
95
|
end
|
95
96
|
|
96
97
|
# ensure that a database exists
|
@@ -109,14 +110,20 @@ module CouchRest
|
|
109
110
|
end
|
110
111
|
|
111
112
|
def paramify_url url, params = {}
|
113
|
+
query = params_to_query(params)
|
114
|
+
query ? "#{url}?#{query}" : url
|
115
|
+
end
|
116
|
+
|
117
|
+
def params_to_query(params)
|
112
118
|
if params && !params.empty?
|
113
119
|
query = params.collect do |k,v|
|
114
120
|
v = MultiJson.encode(v) if %w{key startkey endkey}.include?(k.to_s)
|
115
121
|
"#{k}=#{CGI.escape(v.to_s)}"
|
116
122
|
end.join("&")
|
117
|
-
|
123
|
+
query
|
124
|
+
else
|
125
|
+
nil
|
118
126
|
end
|
119
|
-
url
|
120
127
|
end
|
121
128
|
|
122
129
|
@@decode_json_objects = false
|
@@ -132,15 +139,3 @@ module CouchRest
|
|
132
139
|
end
|
133
140
|
end # class << self
|
134
141
|
end
|
135
|
-
# For the sake of backwards compatability, generate a dummy ExtendedDocument class
|
136
|
-
# which should be replaced by real library: couchrest_extended_document.
|
137
|
-
#
|
138
|
-
# Added 2010-05-10 by Sam Lown. Please remove at some point in the future.
|
139
|
-
#
|
140
|
-
class CouchRest::ExtendedDocument < CouchRest::Document
|
141
|
-
|
142
|
-
def self.inherited(subclass)
|
143
|
-
raise "ExtendedDocument is no longer included in CouchRest base driver, see couchrest_extended_document gem"
|
144
|
-
end
|
145
|
-
|
146
|
-
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
module CouchRest
|
2
|
+
|
3
|
+
# CouchRest Connection
|
4
|
+
#
|
5
|
+
# Handle connections to the CouchDB server and provide a set of HTTP based methods to
|
6
|
+
# perform requests.
|
7
|
+
#
|
8
|
+
# All connection are persistent. A connection cannot be re-used to connect to other servers.
|
9
|
+
#
|
10
|
+
# Six types of REST requests are supported: get, put, post, delete, copy and head.
|
11
|
+
#
|
12
|
+
# Requests that do not have a payload, get, delete and copy, accept the URI and options parameters,
|
13
|
+
# where as put and post both expect a document as the second parameter.
|
14
|
+
#
|
15
|
+
# The API will share the options between the Net::HTTP connection and JSON parser.
|
16
|
+
#
|
17
|
+
# The following options will be recognised as header options and automatically added
|
18
|
+
# to the header hash:
|
19
|
+
#
|
20
|
+
# * `:content_type`, type of content to be sent, especially useful when sending files as this will set the file type. The default is :json.
|
21
|
+
# * `:accept`, the content type to accept in the response. This should pretty much always be `:json`.
|
22
|
+
#
|
23
|
+
# The following request options are supported:
|
24
|
+
#
|
25
|
+
# * `:payload` override the document or data sent in the message body (only PUT or POST).
|
26
|
+
# * `:headers` any additional headers (overrides :content_type and :accept)
|
27
|
+
# * `:timeout` (or `:read_timeout`) and `:open_timeout` the time in miliseconds to wait for the request, see the [Net HTTP Persistent documentation](http://docs.seattlerb.org/net-http-persistent/Net/HTTP/Persistent.html#attribute-i-read_timeout) for more details.
|
28
|
+
# * `:verify_ssl`, `:ssl_client_cert`, `:ssl_client_key`, and `:ssl_ca_file`, SSL handling methods.
|
29
|
+
#
|
30
|
+
# When :raw is true in PUT and POST requests, no attempt will be made to convert the document payload to JSON. This is
|
31
|
+
# not normally necessary as IO and Tempfile objects will not be parsed anyway. The result of the request will
|
32
|
+
# *always* be parsed.
|
33
|
+
#
|
34
|
+
# For all other requests, mainly GET, the :raw option will make no attempt to parse the result. This
|
35
|
+
# is useful for receiving files from the database.
|
36
|
+
#
|
37
|
+
class Connection
|
38
|
+
|
39
|
+
HEADER_CONTENT_SYMBOL_MAP = {
|
40
|
+
:content_type => 'Content-Type',
|
41
|
+
:accept => 'Accept'
|
42
|
+
}
|
43
|
+
|
44
|
+
DEFAULT_HEADERS = {
|
45
|
+
'Content-Type' => 'application/json',
|
46
|
+
'Accept' => 'application/json'
|
47
|
+
}
|
48
|
+
|
49
|
+
KNOWN_PARSER_OPTIONS = [
|
50
|
+
:max_nesting, :allow_nan, :quirks_mode, :create_additions
|
51
|
+
]
|
52
|
+
|
53
|
+
SUCCESS_RESPONSE_CODES = [200, 201, 202, 204]
|
54
|
+
|
55
|
+
attr_reader :uri, :http, :last_response
|
56
|
+
|
57
|
+
def initialize(uri, options = {})
|
58
|
+
raise "CouchRest::Connection.new requires URI::HTTP(S) parameter" unless uri.is_a?(URI::HTTP)
|
59
|
+
@uri = clean_uri(uri)
|
60
|
+
prepare_http_connection(options)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Send a GET request.
|
64
|
+
def get(path, options = {}, &block)
|
65
|
+
execute('GET', path, options, nil, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Send a PUT request.
|
69
|
+
def put(path, doc = nil, options = {})
|
70
|
+
execute('PUT', path, options, doc)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Send a POST request.
|
74
|
+
def post(path, doc = nil, options = {}, &block)
|
75
|
+
execute('POST', path, options, doc, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Send a DELETE request.
|
79
|
+
def delete(path, options = {})
|
80
|
+
execute('DELETE', path, options)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Send a COPY request to the URI provided.
|
84
|
+
def copy(path, destination, options = {})
|
85
|
+
opts = options.nil? ? {} : options.dup
|
86
|
+
opts[:headers] = options[:headers].nil? ? {} : options[:headers].dup
|
87
|
+
opts[:headers]['Destination'] = destination
|
88
|
+
execute('COPY', path, opts)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Send a HEAD request.
|
92
|
+
def head(path, options = {})
|
93
|
+
options = options.merge(:raw => true) # No parsing!
|
94
|
+
execute('HEAD', path, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
protected
|
98
|
+
|
99
|
+
# Duplicate and remove excess baggage from the provided URI
|
100
|
+
def clean_uri(uri)
|
101
|
+
uri = uri.dup
|
102
|
+
uri.path = ""
|
103
|
+
uri.query = nil
|
104
|
+
uri.fragment = nil
|
105
|
+
uri
|
106
|
+
end
|
107
|
+
|
108
|
+
# Take a look at the options povided and try to apply them to the HTTP conneciton.
|
109
|
+
# We try to maintain RestClient compatability as this is what we used before.
|
110
|
+
def prepare_http_connection(opts)
|
111
|
+
@http = HTTPClient.new(opts[:proxy] || self.class.proxy)
|
112
|
+
|
113
|
+
# SSL Certificate option mapping
|
114
|
+
if opts.include?(:verify_ssl)
|
115
|
+
http.ssl_config.verify_mode = opts[:verify_ssl] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
116
|
+
end
|
117
|
+
http.ssl_config.client_cert = opts[:ssl_client_cert] if opts.include?(:ssl_client_cert)
|
118
|
+
http.ssl_config.client_key = opts[:ssl_client_key] if opts.include?(:ssl_client_key)
|
119
|
+
|
120
|
+
# Timeout options
|
121
|
+
http.receive_timeout = opts[:timeout] if opts.include?(:timeout)
|
122
|
+
http.connect_timeout = opts[:open_timeout] if opts.include?(:open_timeout)
|
123
|
+
http.send_timeout = opts[:read_timeout] if opts.include?(:read_timeout)
|
124
|
+
|
125
|
+
http
|
126
|
+
end
|
127
|
+
|
128
|
+
def execute(method, path, options, payload = nil, &block)
|
129
|
+
req = {
|
130
|
+
:method => method,
|
131
|
+
:uri => uri + path
|
132
|
+
}
|
133
|
+
|
134
|
+
# Prepare the request headers
|
135
|
+
DEFAULT_HEADERS.merge(parse_and_convert_request_headers(options)).each do |key, value|
|
136
|
+
req[:header] ||= {}
|
137
|
+
req[:header][key] = value
|
138
|
+
end
|
139
|
+
|
140
|
+
# Prepare the request body, if provided
|
141
|
+
unless payload.nil?
|
142
|
+
req[:body] = payload_from_doc(req, payload, options)
|
143
|
+
end
|
144
|
+
|
145
|
+
send_and_parse_response(req, options, &block)
|
146
|
+
end
|
147
|
+
|
148
|
+
def send_and_parse_response(req, options, &block)
|
149
|
+
if block_given?
|
150
|
+
parser = CouchRest::StreamRowParser.new(options[:continuous] ? :feed : :array)
|
151
|
+
response = send_request(req) do |chunk|
|
152
|
+
parser.parse(chunk) do |doc|
|
153
|
+
block.call(parse_body(doc, options))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
handle_response_code(response)
|
157
|
+
parse_body(parser.header, options)
|
158
|
+
else
|
159
|
+
response = send_request(req)
|
160
|
+
handle_response_code(response)
|
161
|
+
parse_body(response.body, options)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Send request, and leave a reference to the response for debugging purposes
|
166
|
+
def send_request(req, &block)
|
167
|
+
@last_response = http.request(req.delete(:method), req.delete(:uri), req, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
def handle_response_code(response)
|
171
|
+
raise_response_error(response) unless SUCCESS_RESPONSE_CODES.include?(response.status)
|
172
|
+
end
|
173
|
+
|
174
|
+
def parse_body(body, opts)
|
175
|
+
if opts[:raw]
|
176
|
+
# passthru
|
177
|
+
body
|
178
|
+
else
|
179
|
+
MultiJson.load(body, prepare_json_load_options(opts))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Check if the provided doc is nil or special IO device or temp file. If not,
|
184
|
+
# encode it into a string.
|
185
|
+
#
|
186
|
+
# The options supported are:
|
187
|
+
# * :raw TrueClass, if true the payload will not be altered.
|
188
|
+
#
|
189
|
+
def payload_from_doc(req, doc, opts = {})
|
190
|
+
if doc.is_a?(IO) || doc.is_a?(StringIO) || doc.is_a?(Tempfile) # attachments
|
191
|
+
req[:header]['Content-Type'] = mime_for(req[:uri].path)
|
192
|
+
doc
|
193
|
+
elsif opts[:raw] || doc.nil?
|
194
|
+
doc
|
195
|
+
else
|
196
|
+
MultiJson.encode(doc.respond_to?(:as_couch_json) ? doc.as_couch_json : doc)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def mime_for(path)
|
201
|
+
mime = MIME::Types.type_for path
|
202
|
+
mime.empty? ? 'text/plain' : mime[0].content_type
|
203
|
+
end
|
204
|
+
|
205
|
+
def raise_response_error(response)
|
206
|
+
exp = CouchRest::Exceptions::EXCEPTIONS_MAP[response.status]
|
207
|
+
exp ||= CouchRest::RequestFailed
|
208
|
+
raise exp.new(response)
|
209
|
+
end
|
210
|
+
|
211
|
+
def prepare_json_load_options(opts = {})
|
212
|
+
options = {
|
213
|
+
:create_additions => CouchRest.decode_json_objects, # For object conversion, if required
|
214
|
+
:max_nesting => false
|
215
|
+
}
|
216
|
+
KNOWN_PARSER_OPTIONS.each do |k|
|
217
|
+
options[k] = opts[k] if opts.include?(k)
|
218
|
+
end
|
219
|
+
options
|
220
|
+
end
|
221
|
+
|
222
|
+
def parse_and_convert_request_headers(options)
|
223
|
+
headers = options.include?(:headers) ? options[:headers].dup : {}
|
224
|
+
HEADER_CONTENT_SYMBOL_MAP.each do |sym, key|
|
225
|
+
if options.include?(sym)
|
226
|
+
headers[key] = convert_content_type(options[sym])
|
227
|
+
end
|
228
|
+
end
|
229
|
+
headers
|
230
|
+
end
|
231
|
+
|
232
|
+
def convert_content_type(type)
|
233
|
+
if type.is_a?(Symbol)
|
234
|
+
case type
|
235
|
+
when :json
|
236
|
+
'application/json'
|
237
|
+
end
|
238
|
+
else
|
239
|
+
type
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class << self
|
244
|
+
|
245
|
+
# Default proxy URL to use in all connections.
|
246
|
+
attr_accessor :proxy
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
end
|