couchrest 1.2.1 → 2.0.0.beta1
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.
- 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
|
+
[](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
|