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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3d7f21797193d533b8396e309590abd196de7a39
4
- data.tar.gz: caf0fb70ff021d2fadfdca002f9ee86e4ef0ae96
3
+ metadata.gz: 14f12656b8e131a2aaea47e2556e4a6baa80122f
4
+ data.tar.gz: a2cc1b7c8d35eb35ffd0b9ef10083678c3c186f8
5
5
  SHA512:
6
- metadata.gz: a51e81e310ecb977bccd96cbac43aeaef93968eb60a32326ff3adb001c8234470278f732cfc168e73b3b12c72573c2db085e8ebd1653e827c06f03069a5c0ca8
7
- data.tar.gz: e5fec96c5310f5b85f0f0808da98d1cf853554a643b6c5567950fac895e0b8774c439886bd33133cf9b5f7c848ceb433194e800c3ad78d4e03037fe2a46bc2d5
6
+ metadata.gz: 7cada5e2ef0dc7d6f362bacc12a89c02c0d96494ee86dc2eebf443dc02a8967443e296b3759f797a1dffad5bc9d4c1efe0fdfd7c8ad01f8a3a413a0fe01aee8b
7
+ data.tar.gz: 4dc2fee7d91c8927a008a91aa41ec561127ee681221489953627edec08f6bc1d120135e87d6f933dc6f8201aebadafc45df26a92aecf95a6ffda4e7188e1d158
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -4,4 +4,5 @@ rvm:
4
4
  - 2.0.0
5
5
  - 1.9.3
6
6
  - jruby
7
+ - rbx
7
8
  services: couchdb
data/README.md CHANGED
@@ -1,16 +1,15 @@
1
- # CouchRest: CouchDB, close to the metal [![Build Status](https://travis-ci.org/couchrest/couchrest.png)](https://travis-ci.org/couchrest/couchrest)
1
+ # CouchRest: CouchDB, close to the metal
2
2
 
3
- CouchRest is based on [CouchDB's couch.js test
4
- library](http://svn.apache.org/repos/asf/couchdb/trunk/share/www/script/couch.js),
5
- which I find to be concise, clear, and well designed. CouchRest lightly wraps
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 little has changed in the last few year and should work on older versions. Also known to work fine on [Cloudant](http://cloudant.com).
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.1
1
+ 2.0.0.beta1
@@ -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(%q<rest-client>, ["~> 1.8.0"])
31
- s.add_dependency(%q<mime-types>, [">= 1.15"])
32
- s.add_dependency(%q<multi_json>, "~> 1.7", '~> 1.0')
33
- s.add_development_dependency(%q<json>, [">= 1.7.0"])
34
- s.add_development_dependency(%q<rspec>, "~> 2.6.0")
35
- s.add_development_dependency(%q<rake>)
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
@@ -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
@@ -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/monkeypatches'
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, 'couchrest/attributes'
32
- autoload :Server, 'couchrest/server'
33
- autoload :Database, 'couchrest/database'
34
- autoload :Document, 'couchrest/document'
35
- autoload :Design, 'couchrest/design'
36
- autoload :Model, 'couchrest/model'
37
- autoload :Pager, 'couchrest/helper/pager'
38
- autoload :Streamer, 'couchrest/helper/streamer'
39
- autoload :Attachments, 'couchrest/helper/attachments'
40
- autoload :Upgrade, 'couchrest/helper/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
- # todo, make this parse the url and instantiate a Server or Database instance
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
- # set proxy to use
92
+ # Set default proxy to use in connections
92
93
  def proxy url
93
- RestClient.proxy = url
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
- url = "#{url}?#{query}"
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