sovaa 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ # Sovaa is a CouchRest fork of a CouchRest fork
2
+
3
+ * Added a bit of bulk back in
4
+ * Use YAJL
@@ -0,0 +1,99 @@
1
+ # Copyright 2008 J. Chris Anderson
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'yajl'
15
+
16
+ require 'sova/http'
17
+ require 'sova/server'
18
+ require 'sova/database'
19
+
20
+ Sovaa::HTTP.adapter = :net_http
21
+
22
+ # = CouchDB, close to the metal
23
+ module Sovaa
24
+ VERSION = '1.0.1'
25
+
26
+ # The Sovaa module methods handle the basic JSON serialization
27
+ # and deserialization, as well as query parameters. The module also includes
28
+ # some helpers for tasks like instantiating a new Database or Server instance.
29
+ class << self
30
+
31
+ # todo, make this parse the url and instantiate a Server or Database instance
32
+ # depending on the specificity.
33
+ def new(*opts)
34
+ Server.new(*opts)
35
+ end
36
+
37
+ def parse url
38
+ case url
39
+ when /^(https?:\/\/)(.*)\/(.*)\/(.*)/
40
+ scheme = $1
41
+ host = $2
42
+ db = $3
43
+ docid = $4
44
+ when /^(https?:\/\/)(.*)\/(.*)/
45
+ scheme = $1
46
+ host = $2
47
+ db = $3
48
+ when /^(https?:\/\/)(.*)/
49
+ scheme = $1
50
+ host = $2
51
+ when /(.*)\/(.*)\/(.*)/
52
+ host = $1
53
+ db = $2
54
+ docid = $3
55
+ when /(.*)\/(.*)/
56
+ host = $1
57
+ db = $2
58
+ else
59
+ db = url
60
+ end
61
+
62
+ db = nil if db && db.empty?
63
+
64
+ {
65
+ :host => (scheme || "http://") + (host || "127.0.0.1:5984"),
66
+ :database => db,
67
+ :doc => docid
68
+ }
69
+ end
70
+
71
+ attr_accessor :proxy
72
+
73
+ # ensure that a database exists
74
+ # creates it if it isn't already there
75
+ # returns it after it's been created
76
+ def database! url
77
+ parsed = parse url
78
+ cr = Sovaa.new(parsed[:host])
79
+ cr.database!(parsed[:database])
80
+ end
81
+
82
+ def database url
83
+ parsed = parse url
84
+ cr = Sovaa.new(parsed[:host])
85
+ cr.database(parsed[:database])
86
+ end
87
+
88
+ def paramify_url url, params = {}
89
+ if params && !params.empty?
90
+ query = params.collect do |k,v|
91
+ v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
92
+ "#{k}=#{CGI.escape(v.to_s)}"
93
+ end.join("&")
94
+ url = "#{url}?#{query}"
95
+ end
96
+ url
97
+ end
98
+ end # class << self
99
+ end
@@ -0,0 +1,349 @@
1
+ require 'cgi'
2
+ require "base64"
3
+
4
+ module Sovaa
5
+ class Database
6
+ attr_reader :server, :host, :name, :root, :uri
7
+ attr_accessor :bulk_save_cache_limit
8
+
9
+ # Create a Sovaa::Database adapter for the supplied Sova::Server
10
+ # and database name.
11
+ #
12
+ # ==== Parameters
13
+ # server<Sovaa::Server>:: database host
14
+ # name<String>:: database name
15
+ #
16
+ def initialize(server, name)
17
+ @name = name
18
+ @server = server
19
+ @host = server.uri
20
+ @uri = "/#{name.gsub('/','%2F')}"
21
+ @root = host + uri
22
+ @bulk_save_cache = []
23
+ @bulk_save_cache_limit = 500 # must be smaller than the uuid count
24
+ end
25
+
26
+ # returns the database's uri
27
+ def to_s
28
+ @root
29
+ end
30
+
31
+ # GET the database info from CouchDB
32
+ def info
33
+ HTTP.get @root
34
+ end
35
+
36
+ # Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
37
+ def documents(params = {})
38
+ keys = params.delete(:keys)
39
+ url = Sovaa.paramify_url "#{@root}/_all_docs", params
40
+ if keys
41
+ HTTP.post(url, {:keys => keys})
42
+ else
43
+ HTTP.get url
44
+ end
45
+ end
46
+
47
+ # load a set of documents by passing an array of ids
48
+ def get_bulk(ids)
49
+ documents(:keys => ids, :include_docs => true)
50
+ end
51
+ alias :bulk_load :get_bulk
52
+
53
+ # POST a temporary view function to CouchDB for querying. This is not
54
+ # recommended, as you don't get any performance benefit from CouchDB's
55
+ # materialized views. Can be quite slow on large databases.
56
+ def slow_view(funcs, params = {})
57
+ keys = params.delete(:keys)
58
+ funcs = funcs.merge({:keys => keys}) if keys
59
+ url = Sovaa.paramify_url "#{@root}/_temp_view", params
60
+ HTTP.post(url, funcs)
61
+ end
62
+
63
+ # backwards compatibility is a plus
64
+ alias :temp_view :slow_view
65
+
66
+ # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
67
+ # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
68
+ def view(name, params = {}, &block)
69
+ keys = params.delete(:keys)
70
+ name = name.split('/') # I think this will always be length == 2, but maybe not...
71
+ dname = name.shift
72
+ vname = name.join('/')
73
+ url = Sovaa.paramify_url "#{@root}/_design/#{dname}/_view/#{vname}", params
74
+ if keys
75
+ HTTP.post(url, {:keys => keys})
76
+ else
77
+ HTTP.get url
78
+ end
79
+ end
80
+
81
+ # GET a document from CouchDB, by id. Returns a Ruby Hash.
82
+ def get(id, params = {})
83
+ slug = escape_docid(id)
84
+ url = Sovaa.paramify_url("#{@root}/#{slug}", params)
85
+ result = HTTP.get(url)
86
+ end
87
+
88
+ # GET an attachment directly from CouchDB
89
+ def fetch_attachment(doc, name)
90
+ uri = url_for_attachment(doc, name)
91
+ HTTP.request(:get, uri).body
92
+ end
93
+
94
+ # PUT an attachment directly to CouchDB
95
+ def put_attachment(doc, name, file, options = {})
96
+ docid = escape_docid(doc['_id'])
97
+ uri = url_for_attachment(doc, name)
98
+ response = HTTP.request(:put, uri, file, options)
99
+ JsonResponse.new(response.body, response.headers['ETag'])
100
+ end
101
+
102
+ # DELETE an attachment directly from CouchDB
103
+ def delete_attachment(doc, name, force=false)
104
+ uri = url_for_attachment(doc, name)
105
+ # this needs a rev
106
+ begin
107
+ HTTP.delete(uri)
108
+ rescue Exception => error
109
+ if force
110
+ # get over a 409
111
+ doc = get(doc['_id'])
112
+ uri = url_for_attachment(doc, name)
113
+ HTTP.delete(uri)
114
+ else
115
+ raise error
116
+ end
117
+ end
118
+ end
119
+
120
+ # Save a document to CouchDB. This will use the <tt>_id</tt> field from
121
+ # the document as the id for PUT, or request a new UUID from CouchDB, if
122
+ # no <tt>_id</tt> is present on the document. IDs are attached to
123
+ # documents on the client side because POST has the curious property of
124
+ # being automatically retried by proxies in the event of network
125
+ # segmentation and lost responses.
126
+ #
127
+ # If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
128
+ # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
129
+ #
130
+ # If <tt>batch</tt> is true (false by default) the document is saved in
131
+ # batch mode, "used to achieve higher throughput at the cost of lower
132
+ # guarantees. When [...] sent using this option, it is not immediately
133
+ # written to disk. Instead it is stored in memory on a per-user basis for a
134
+ # second or so (or the number of docs in memory reaches a certain point).
135
+ # After the threshold has passed, the docs are committed to disk. Instead
136
+ # of waiting for the doc to be written to disk before responding, CouchDB
137
+ # sends an HTTP 202 Accepted response immediately. batch=ok is not suitable
138
+ # for crucial data, but it ideal for applications like logging which can
139
+ # accept the risk that a small proportion of updates could be lost due to a
140
+ # crash."
141
+ def save_doc(doc, bulk = false, batch = false)
142
+ if doc['_attachments']
143
+ doc['_attachments'] = encode_attachments(doc['_attachments'])
144
+ end
145
+ if bulk
146
+ @bulk_save_cache << doc
147
+ bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
148
+ return {"ok" => true} # Compatibility with Document#save
149
+ elsif !bulk && @bulk_save_cache.length > 0
150
+ bulk_save
151
+ end
152
+ result = if doc['_id']
153
+ slug = escape_docid(doc['_id'])
154
+ begin
155
+ uri = "#{@root}/#{slug}"
156
+ uri << "?batch=ok" if batch
157
+ HTTP.put uri, doc
158
+ rescue Sovaa::NotFound
159
+ p "resource not found when saving even tho an id was passed"
160
+ slug = doc['_id'] = @server.next_uuid
161
+ HTTP.put "#{@root}/#{slug}", doc
162
+ end
163
+ else
164
+ begin
165
+ slug = doc['_id'] = @server.next_uuid
166
+ HTTP.put "#{@root}/#{slug}", doc
167
+ rescue #old version of couchdb
168
+ HTTP.post @root, doc
169
+ end
170
+ end
171
+ if result['ok']
172
+ doc['_id'] = result['id']
173
+ doc['_rev'] = result['rev']
174
+ doc.database = self if doc.respond_to?(:database=)
175
+ end
176
+ result
177
+ end
178
+
179
+ # Save a document to CouchDB in bulk mode. See #save_doc's +bulk+ argument.
180
+ def bulk_save_doc(doc)
181
+ save_doc(doc, true)
182
+ end
183
+
184
+ # Save a document to CouchDB in batch mode. See #save_doc's +batch+ argument.
185
+ def batch_save_doc(doc)
186
+ save_doc(doc, false, true)
187
+ end
188
+
189
+ # POST an array of documents to CouchDB. If any of the documents are
190
+ # missing ids, supply one from the uuid cache.
191
+ #
192
+ # If called with no arguments, bulk saves the cache of documents to be bulk saved.
193
+ def bulk_save(docs = nil, use_uuids = true)
194
+ if docs.nil?
195
+ docs = @bulk_save_cache
196
+ @bulk_save_cache = []
197
+ end
198
+ if (use_uuids)
199
+ ids, noids = docs.partition{|d|d['_id']}
200
+ uuid_count = [noids.length, @server.uuid_batch_count].max
201
+ noids.each do |doc|
202
+ nextid = @server.next_uuid(uuid_count) rescue nil
203
+ doc['_id'] = nextid if nextid
204
+ end
205
+ end
206
+ HTTP.post "#{@root}/_bulk_docs", {:docs => docs}
207
+ end
208
+ alias :bulk_delete :bulk_save
209
+
210
+ # DELETE the document from CouchDB that has the given <tt>_id</tt> and
211
+ # <tt>_rev</tt>.
212
+ #
213
+ # If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
214
+ # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
215
+ def delete_doc(doc, bulk = false)
216
+ raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
217
+ if bulk
218
+ @bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
219
+ return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
220
+ return { "ok" => true } # Mimic the non-deferred version
221
+ end
222
+ slug = escape_docid(doc['_id'])
223
+ HTTP.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
224
+ end
225
+
226
+ # COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
227
+ # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
228
+ # hash with a '_rev' key
229
+ def copy_doc(doc, dest)
230
+ raise ArgumentError, "_id is required for copying" unless doc['_id']
231
+ slug = escape_docid(doc['_id'])
232
+ destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
233
+ "#{dest['_id']}?rev=#{dest['_rev']}"
234
+ else
235
+ dest
236
+ end
237
+ HTTP.copy "#{@root}/#{slug}", destination
238
+ end
239
+
240
+ # Updates the given doc by yielding the current state of the doc
241
+ # and trying to update update_limit times. Returns the new doc
242
+ # if the doc was successfully updated without hitting the limit
243
+ def update_doc(doc_id, params = {}, update_limit=10)
244
+ resp = {'ok' => false}
245
+ new_doc = nil
246
+ last_fail = nil
247
+
248
+ until resp['ok'] or update_limit <= 0
249
+ doc = self.get(doc_id, params) # grab the doc
250
+ new_doc = yield doc # give it to the caller to be updated
251
+ begin
252
+ resp = self.save_doc new_doc # try to PUT the updated doc into the db
253
+ rescue Sovaa::Conflict => e
254
+ update_limit -= 1
255
+ last_fail = e
256
+ end
257
+ end
258
+
259
+ raise last_fail unless resp['ok']
260
+ new_doc
261
+ end
262
+
263
+ # Compact the database, removing old document revisions and optimizing space use.
264
+ def compact!
265
+ HTTP.post "#{@root}/_compact"
266
+ end
267
+
268
+ # Create the database
269
+ def create!
270
+ bool = server.create_db(@name) rescue false
271
+ bool && true
272
+ end
273
+
274
+ # Delete and re create the database
275
+ def recreate!
276
+ delete!
277
+ create!
278
+ rescue Sovaa::NotFound
279
+ ensure
280
+ create!
281
+ end
282
+
283
+ # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
284
+ def replicate_from(other_db, continuous = false, create_target = false)
285
+ replicate(other_db, continuous, :target => name, :create_target => create_target)
286
+ end
287
+
288
+ # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
289
+ def replicate_to(other_db, continuous = false, create_target = false)
290
+ replicate(other_db, continuous, :source => name, :create_target => create_target)
291
+ end
292
+
293
+ # DELETE the database itself. This is not undoable and could be rather
294
+ # catastrophic. Use with care!
295
+ def delete!
296
+ HTTP.delete @root
297
+ end
298
+
299
+ private
300
+
301
+ def replicate(other_db, continuous, options)
302
+ raise ArgumentError, "must provide a Sovaa::Database" unless other_db.kind_of?(Sova::Database)
303
+ raise ArgumentError, "must provide a target or source option" unless (options.key?(:target) || options.key?(:source))
304
+ payload = options
305
+ if options.has_key?(:target)
306
+ payload[:source] = other_db.root
307
+ else
308
+ payload[:target] = other_db.root
309
+ end
310
+ payload[:continuous] = continuous
311
+ HTTP.post "#{@host}/_replicate", payload
312
+ end
313
+
314
+ def uri_for_attachment(doc, name)
315
+ if doc.is_a?(String)
316
+ puts "Sovaa::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
317
+ docid = doc
318
+ rev = nil
319
+ else
320
+ docid = doc['_id']
321
+ rev = doc['_rev']
322
+ end
323
+ docid = escape_docid(docid)
324
+ name = CGI.escape(name)
325
+ rev = "?rev=#{doc['_rev']}" if rev
326
+ "/#{docid}/#{name}#{rev}"
327
+ end
328
+
329
+ def url_for_attachment(doc, name)
330
+ @root + uri_for_attachment(doc, name)
331
+ end
332
+
333
+ def escape_docid id
334
+ /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
335
+ end
336
+
337
+ def encode_attachments(attachments)
338
+ attachments.each do |k,v|
339
+ next if v['stub']
340
+ v['data'] = base64(v['data'])
341
+ end
342
+ attachments
343
+ end
344
+
345
+ def base64(data)
346
+ Base64.encode64(data).gsub(/\s/,'')
347
+ end
348
+ end
349
+ end
@@ -0,0 +1,85 @@
1
+ require "httpi"
2
+ require "uri"
3
+ require "forwardable"
4
+ require 'sovaa/json_response'
5
+
6
+ module Sovaa
7
+ class HTTPError < StandardError
8
+ extend Forwardable
9
+
10
+ def_delegators :@response, :code, :headers, :body
11
+
12
+ def initialize response
13
+ @response = response
14
+ end
15
+ end
16
+ class NotFound < HTTPError; end
17
+ class Conflict < HTTPError; end
18
+ class Invalid < HTTPError; end
19
+
20
+ module HTTP
21
+ extend self
22
+
23
+ attr_accessor :adapter
24
+
25
+ def request method, uri, doc=nil, headers={}
26
+ uri = URI.parse(uri)
27
+ request = HTTPI::Request.new
28
+ request.url = uri
29
+ request.auth.basic uri.user, uri.password if uri.user && uri.password
30
+ request.proxy = Sovaa.proxy if Sova.proxy
31
+ request.body = doc if doc
32
+ request.headers = {
33
+ "Content-Type" => "application/json",
34
+ "Accept" => "application/json"
35
+ }.merge(headers)
36
+
37
+ response = HTTPI.request(method, request, adapter)
38
+ raise http_error(response) if response.error?
39
+ response
40
+ end
41
+
42
+ def put(uri, doc=nil, headers={})
43
+ doc = Yajl::Encoder.encode(doc) if doc
44
+ response = request(:put, uri, doc, headers)
45
+ JsonResponse.new(response.body, response.headers['ETag'])
46
+ end
47
+
48
+ def get(uri)
49
+ response = request(:get, uri)
50
+ JsonResponse.new(response.body, response.headers['ETag'])
51
+ end
52
+
53
+ def post(uri, doc=nil)
54
+ doc = Yajl::Encoder.encode(doc) if doc
55
+ response = request(:post, uri, doc)
56
+ JsonResponse.new(response.body, response.headers['ETag'])
57
+ end
58
+
59
+ def delete(uri)
60
+ response = request(:delete, uri)
61
+ JsonResponse.new(response.body, response.headers['ETag'])
62
+ end
63
+
64
+ def copy(uri, destination)
65
+ headers = {'X-HTTP-Method-Override' => 'COPY', 'Destination' => destination}
66
+ response = request(:post, uri, nil, headers)
67
+ JsonResponse.new(response.body, response.headers['ETag'])
68
+ end
69
+
70
+ private
71
+
72
+ def http_error response
73
+ klass =
74
+ case response.code
75
+ when 404 then
76
+ NotFound
77
+ when 409 then
78
+ Conflict
79
+ else
80
+ HTTPError
81
+ end
82
+ klass.new(response)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,9 @@
1
+ module Sovaa
2
+ class JsonResponse < Hash
3
+ attr_reader :etag
4
+ def initialize(json, etag)
5
+ super(Yajl::Parser.parse(json))
6
+ @etag = etag
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,50 @@
1
+ module Sovaa
2
+ class Server
3
+ attr_accessor :uri, :uuid_batch_count, :available_databases
4
+ def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
5
+ @uri = server
6
+ @uuid_batch_count = uuid_batch_count
7
+ end
8
+
9
+ # Lists all databases on the server
10
+ def databases
11
+ HTTP.get "#{@uri}/_all_dbs"
12
+ end
13
+
14
+ # Returns a Sovaa::Database for the given name
15
+ def database(name)
16
+ Sovaa::Database.new(self, name)
17
+ end
18
+
19
+ # Creates the database if it doesn't exist
20
+ def database!(name)
21
+ create_db(name) rescue nil
22
+ database(name)
23
+ end
24
+
25
+ # GET the welcome message
26
+ def info
27
+ HTTP.get "#{@uri}/"
28
+ end
29
+
30
+ # Create a database
31
+ def create_db(name)
32
+ HTTP.put "#{@uri}/#{name}"
33
+ database(name)
34
+ end
35
+
36
+ # Restart the CouchDB instance
37
+ def restart!
38
+ HTTP.post "#{@uri}/_restart"
39
+ end
40
+
41
+ # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
42
+ def next_uuid(count = @uuid_batch_count)
43
+ @uuids ||= []
44
+ if @uuids.empty?
45
+ @uuids = HTTP.get("#{@uri}/_uuids?count=#{count}")["uuids"]
46
+ end
47
+ @uuids.pop
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,26 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "sovaa"
3
+ s.version = "0.0.1"
4
+ s.date = "2011-02-17"
5
+ s.summary = "CouchDB library"
6
+ s.email = "jonathan.stott@gmail.com"
7
+ s.homepage = "http://github.com/namelessjon/couchrest"
8
+ s.has_rdoc = true
9
+ s.authors = ["Jonathan Stott"]
10
+ s.files = [
11
+ "README.md",
12
+ "sovaa.gemspec",
13
+ "lib/sovaa.rb",
14
+ "lib/sovaa/database.rb",
15
+ "lib/sovaa/json_response.rb",
16
+ "lib/sovaa/http.rb",
17
+ "lib/sovaa/server.rb"
18
+ ]
19
+
20
+ s.add_dependency "httpi", "~> 0.8"
21
+ s.add_dependency "yajl-ruby"
22
+ s.add_development_dependency "rspec-core", "~> 2.5.1"
23
+ s.add_development_dependency "rspec-mocks", "~> 2.5.0"
24
+ s.add_development_dependency "rspec-expectations", "~> 2.5.0"
25
+ end
26
+
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sovaa
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Jonathan Stott
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-02-17 00:00:00 +00:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: httpi
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: "0.8"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: yajl-ruby
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec-core
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 2.5.1
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec-mocks
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: 2.5.0
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: rspec-expectations
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 2.5.0
69
+ type: :development
70
+ version_requirements: *id005
71
+ description:
72
+ email: jonathan.stott@gmail.com
73
+ executables: []
74
+
75
+ extensions: []
76
+
77
+ extra_rdoc_files: []
78
+
79
+ files:
80
+ - README.md
81
+ - sovaa.gemspec
82
+ - lib/sovaa.rb
83
+ - lib/sovaa/database.rb
84
+ - lib/sovaa/json_response.rb
85
+ - lib/sovaa/http.rb
86
+ - lib/sovaa/server.rb
87
+ has_rdoc: true
88
+ homepage: http://github.com/namelessjon/couchrest
89
+ licenses: []
90
+
91
+ post_install_message:
92
+ rdoc_options: []
93
+
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.5.2
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: CouchDB library
115
+ test_files: []
116
+