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 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