sovaa 0.0.1

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.
@@ -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
+