couchrest 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +69 -0
  3. data/Rakefile +103 -0
  4. data/THANKS.md +18 -0
  5. data/bin/couchdir +20 -0
  6. data/examples/model/example.rb +138 -0
  7. data/examples/word_count/markov +38 -0
  8. data/examples/word_count/views/books/chunked-map.js +3 -0
  9. data/examples/word_count/views/books/united-map.js +1 -0
  10. data/examples/word_count/views/markov/chain-map.js +6 -0
  11. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  12. data/examples/word_count/views/word_count/count-map.js +6 -0
  13. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  14. data/examples/word_count/word_count.rb +67 -0
  15. data/examples/word_count/word_count_query.rb +39 -0
  16. data/lib/couchrest/commands/generate.rb +71 -0
  17. data/lib/couchrest/commands/push.rb +103 -0
  18. data/lib/couchrest/core/database.rb +239 -0
  19. data/lib/couchrest/core/design.rb +89 -0
  20. data/lib/couchrest/core/document.rb +63 -0
  21. data/lib/couchrest/core/model.rb +607 -0
  22. data/lib/couchrest/core/server.rb +51 -0
  23. data/lib/couchrest/core/view.rb +4 -0
  24. data/lib/couchrest/helper/pager.rb +103 -0
  25. data/lib/couchrest/helper/streamer.rb +44 -0
  26. data/lib/couchrest/monkeypatches.rb +24 -0
  27. data/lib/couchrest.rb +140 -0
  28. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  29. data/spec/couchrest/core/database_spec.rb +630 -0
  30. data/spec/couchrest/core/design_spec.rb +131 -0
  31. data/spec/couchrest/core/document_spec.rb +130 -0
  32. data/spec/couchrest/core/model_spec.rb +855 -0
  33. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  34. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  35. data/spec/fixtures/attachments/README +3 -0
  36. data/spec/fixtures/attachments/couchdb.png +0 -0
  37. data/spec/fixtures/attachments/test.html +11 -0
  38. data/spec/fixtures/views/lib.js +3 -0
  39. data/spec/fixtures/views/test_view/lib.js +3 -0
  40. data/spec/fixtures/views/test_view/only-map.js +4 -0
  41. data/spec/fixtures/views/test_view/test-map.js +3 -0
  42. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  43. data/spec/spec.opts +6 -0
  44. data/spec/spec_helper.rb +20 -0
  45. data/utils/remap.rb +27 -0
  46. data/utils/subset.rb +30 -0
  47. metadata +156 -0
@@ -0,0 +1,607 @@
1
+ require 'rubygems'
2
+ require 'extlib'
3
+ require 'digest/md5'
4
+ require File.dirname(__FILE__) + '/document'
5
+ require 'mime/types'
6
+
7
+ # = CouchRest::Model - Document modeling, the CouchDB way
8
+ module CouchRest
9
+ # = CouchRest::Model - Document modeling, the CouchDB way
10
+ #
11
+ # CouchRest::Model provides an ORM-like interface for CouchDB documents. It
12
+ # avoids all usage of <tt>method_missing</tt>, and tries to strike a balance
13
+ # between usability and magic. See CouchRest::Model#view_by for
14
+ # documentation about the view-generation system.
15
+ #
16
+ # ==== Example
17
+ #
18
+ # This is an example class using CouchRest::Model. It is taken from the
19
+ # spec/couchrest/core/model_spec.rb file, which may be even more up to date
20
+ # than this example.
21
+ #
22
+ # class Article < CouchRest::Model
23
+ # use_database CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
24
+ # unique_id :slug
25
+ #
26
+ # view_by :date, :descending => true
27
+ # view_by :user_id, :date
28
+ #
29
+ # view_by :tags,
30
+ # :map =>
31
+ # "function(doc) {
32
+ # if (doc['couchrest-type'] == 'Article' && doc.tags) {
33
+ # doc.tags.forEach(function(tag){
34
+ # emit(tag, 1);
35
+ # });
36
+ # }
37
+ # }",
38
+ # :reduce =>
39
+ # "function(keys, values, rereduce) {
40
+ # return sum(values);
41
+ # }"
42
+ #
43
+ # key_writer :date
44
+ # key_reader :slug, :created_at, :updated_at
45
+ # key_accessor :title, :tags
46
+ #
47
+ # timestamps!
48
+ #
49
+ # before(:create, :generate_slug_from_title)
50
+ # def generate_slug_from_title
51
+ # self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'')
52
+ # end
53
+ # end
54
+ #
55
+ # ==== Examples of finding articles with these views:
56
+ #
57
+ # * All the articles by Barney published in the last 24 hours. Note that we
58
+ # use <tt>{}</tt> as a special value that sorts after all strings,
59
+ # numbers, and arrays.
60
+ #
61
+ # Article.by_user_id_and_date :startkey => ["barney", Time.now - 24 * 3600], :endkey => ["barney", {}]
62
+ #
63
+ # * The most recent 20 articles. Remember that the <tt>view_by :date</tt>
64
+ # has the default option <tt>:descending => true</tt>.
65
+ #
66
+ # Article.by_date :limit => 20
67
+ #
68
+ # * The raw CouchDB view reduce result for the custom <tt>:tags</tt> view.
69
+ # In this case we'll get a count of the number of articles tagged "ruby".
70
+ #
71
+ # Article.by_tags :key => "ruby", :reduce => true
72
+ #
73
+ class Model < Document
74
+
75
+ # instantiates the hash by converting all the keys to strings.
76
+ def initialize keys = {}
77
+ super(keys)
78
+ apply_defaults
79
+ cast_keys
80
+ unless self['_id'] && self['_rev']
81
+ self['couchrest-type'] = self.class.to_s
82
+ end
83
+ end
84
+
85
+ # this is the CouchRest::Database that model classes will use unless
86
+ # they override it with <tt>use_database</tt>
87
+ cattr_accessor :default_database
88
+
89
+ class_inheritable_accessor :casts
90
+ class_inheritable_accessor :default_obj
91
+ class_inheritable_accessor :class_database
92
+ class_inheritable_accessor :design_doc
93
+ class_inheritable_accessor :design_doc_slug_cache
94
+ class_inheritable_accessor :design_doc_fresh
95
+
96
+ class << self
97
+ # override the CouchRest::Model-wide default_database
98
+ def use_database db
99
+ self.class_database = db
100
+ end
101
+
102
+ # returns the CouchRest::Database instance that this class uses
103
+ def database
104
+ self.class_database || CouchRest::Model.default_database
105
+ end
106
+
107
+ # Load a document from the database by id
108
+ def get id
109
+ doc = database.get id
110
+ new(doc)
111
+ end
112
+
113
+ # Load all documents that have the "couchrest-type" field equal to the
114
+ # name of the current class. Take the standard set of
115
+ # CouchRest::Database#view options.
116
+ def all opts = {}, &block
117
+ self.design_doc ||= Design.new(default_design_doc)
118
+ unless design_doc_fresh
119
+ refresh_design_doc
120
+ end
121
+ view :all, opts, &block
122
+ end
123
+
124
+ # Load the first document that have the "couchrest-type" field equal to
125
+ # the name of the current class.
126
+ #
127
+ # ==== Returns
128
+ # Object:: The first object instance available
129
+ # or
130
+ # Nil:: if no instances available
131
+ #
132
+ # ==== Parameters
133
+ # opts<Hash>::
134
+ # View options, see <tt>CouchRest::Database#view</tt> options for more info.
135
+ def first opts = {}
136
+ first_instance = self.all(opts.merge!(:limit => 1))
137
+ first_instance.empty? ? nil : first_instance.first
138
+ end
139
+
140
+ # Cast a field as another class. The class must be happy to have the
141
+ # field's primitive type as the argument to it's constuctur. Classes
142
+ # which inherit from CouchRest::Model are happy to act as sub-objects
143
+ # for any fields that are stored in JSON as object (and therefore are
144
+ # parsed from the JSON as Ruby Hashes).
145
+ #
146
+ # Example:
147
+ #
148
+ # class Post < CouchRest::Model
149
+ #
150
+ # key_accessor :title, :body, :author
151
+ #
152
+ # cast :author, :as => 'Author'
153
+ #
154
+ # end
155
+ #
156
+ # post.author.class #=> Author
157
+ #
158
+ # Using the same example, if a Post should have many Comments, we
159
+ # would declare it like this:
160
+ #
161
+ # class Post < CouchRest::Model
162
+ #
163
+ # key_accessor :title, :body, :author, comments
164
+ #
165
+ # cast :author, :as => 'Author'
166
+ # cast :comments, :as => ['Comment']
167
+ #
168
+ # end
169
+ #
170
+ # post.author.class #=> Author
171
+ # post.comments.class #=> Array
172
+ # post.comments.first #=> Comment
173
+ #
174
+ def cast field, opts = {}
175
+ self.casts ||= {}
176
+ self.casts[field.to_s] = opts
177
+ end
178
+
179
+ # Defines methods for reading and writing from fields in the document.
180
+ # Uses key_writer and key_reader internally.
181
+ def key_accessor *keys
182
+ key_writer *keys
183
+ key_reader *keys
184
+ end
185
+
186
+ # For each argument key, define a method <tt>key=</tt> that sets the
187
+ # corresponding field on the CouchDB document.
188
+ def key_writer *keys
189
+ keys.each do |method|
190
+ key = method.to_s
191
+ define_method "#{method}=" do |value|
192
+ self[key] = value
193
+ end
194
+ end
195
+ end
196
+
197
+ # For each argument key, define a method <tt>key</tt> that reads the
198
+ # corresponding field on the CouchDB document.
199
+ def key_reader *keys
200
+ keys.each do |method|
201
+ key = method.to_s
202
+ define_method method do
203
+ self[key]
204
+ end
205
+ end
206
+ end
207
+
208
+ def default
209
+ self.default_obj
210
+ end
211
+
212
+ def set_default hash
213
+ self.default_obj = hash
214
+ end
215
+
216
+ # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
217
+ # on the document whenever saving occurs. CouchRest uses a pretty
218
+ # decent time format by default. See Time#to_json
219
+ def timestamps!
220
+ before(:save) do
221
+ self['updated_at'] = Time.now
222
+ self['created_at'] = self['updated_at'] if new_document?
223
+ end
224
+ end
225
+
226
+ # Name a method that will be called before the document is first saved,
227
+ # which returns a string to be used for the document's <tt>_id</tt>.
228
+ # Because CouchDB enforces a constraint that each id must be unique,
229
+ # this can be used to enforce eg: uniq usernames. Note that this id
230
+ # must be globally unique across all document types which share a
231
+ # database, so if you'd like to scope uniqueness to this class, you
232
+ # should use the class name as part of the unique id.
233
+ def unique_id method = nil, &block
234
+ if method
235
+ define_method :set_unique_id do
236
+ self['_id'] ||= self.send(method)
237
+ end
238
+ elsif block
239
+ define_method :set_unique_id do
240
+ uniqid = block.call(self)
241
+ raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
242
+ self['_id'] ||= uniqid
243
+ end
244
+ end
245
+ end
246
+
247
+ # Define a CouchDB view. The name of the view will be the concatenation
248
+ # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
249
+ #
250
+ # ==== Example views:
251
+ #
252
+ # class Post
253
+ # # view with default options
254
+ # # query with Post.by_date
255
+ # view_by :date, :descending => true
256
+ #
257
+ # # view with compound sort-keys
258
+ # # query with Post.by_user_id_and_date
259
+ # view_by :user_id, :date
260
+ #
261
+ # # view with custom map/reduce functions
262
+ # # query with Post.by_tags :reduce => true
263
+ # view_by :tags,
264
+ # :map =>
265
+ # "function(doc) {
266
+ # if (doc['couchrest-type'] == 'Post' && doc.tags) {
267
+ # doc.tags.forEach(function(tag){
268
+ # emit(doc.tag, 1);
269
+ # });
270
+ # }
271
+ # }",
272
+ # :reduce =>
273
+ # "function(keys, values, rereduce) {
274
+ # return sum(values);
275
+ # }"
276
+ # end
277
+ #
278
+ # <tt>view_by :date</tt> will create a view defined by this Javascript
279
+ # function:
280
+ #
281
+ # function(doc) {
282
+ # if (doc['couchrest-type'] == 'Post' && doc.date) {
283
+ # emit(doc.date, null);
284
+ # }
285
+ # }
286
+ #
287
+ # It can be queried by calling <tt>Post.by_date</tt> which accepts all
288
+ # valid options for CouchRest::Database#view. In addition, calling with
289
+ # the <tt>:raw => true</tt> option will return the view rows
290
+ # themselves. By default <tt>Post.by_date</tt> will return the
291
+ # documents included in the generated view.
292
+ #
293
+ # CouchRest::Database#view options can be applied at view definition
294
+ # time as defaults, and they will be curried and used at view query
295
+ # time. Or they can be overridden at query time.
296
+ #
297
+ # Custom views can be queried with <tt>:reduce => true</tt> to return
298
+ # reduce results. The default for custom views is to query with
299
+ # <tt>:reduce => false</tt>.
300
+ #
301
+ # Views are generated (on a per-model basis) lazily on first-access.
302
+ # This means that if you are deploying changes to a view, the views for
303
+ # that model won't be available until generation is complete. This can
304
+ # take some time with large databases. Strategies are in the works.
305
+ #
306
+ # To understand the capabilities of this view system more compeletly,
307
+ # it is recommended that you read the RSpec file at
308
+ # <tt>spec/core/model_spec.rb</tt>.
309
+
310
+ def view_by *keys
311
+ self.design_doc ||= Design.new(default_design_doc)
312
+ opts = keys.pop if keys.last.is_a?(Hash)
313
+ opts ||= {}
314
+ ducktype = opts.delete(:ducktype)
315
+ unless ducktype || opts[:map]
316
+ opts[:guards] ||= []
317
+ opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
318
+ end
319
+ keys.push opts
320
+ self.design_doc.view_by(*keys)
321
+ self.design_doc_fresh = false
322
+ end
323
+
324
+ def method_missing m, *args
325
+ if has_view?(m)
326
+ query = args.shift || {}
327
+ view(m, query, *args)
328
+ else
329
+ super
330
+ end
331
+ end
332
+
333
+ # returns stored defaults if the there is a view named this in the design doc
334
+ def has_view?(view)
335
+ view = view.to_s
336
+ design_doc && design_doc['views'] && design_doc['views'][view]
337
+ end
338
+
339
+ # Dispatches to any named view.
340
+ def view name, query={}, &block
341
+ unless design_doc_fresh
342
+ refresh_design_doc
343
+ end
344
+ query[:raw] = true if query[:reduce]
345
+ raw = query.delete(:raw)
346
+ fetch_view_with_docs(name, query, raw, &block)
347
+ end
348
+
349
+ def all_design_doc_versions
350
+ database.documents :startkey => "_design/#{self.to_s}-",
351
+ :endkey => "_design/#{self.to_s}-\u9999"
352
+ end
353
+
354
+ # Deletes any non-current design docs that were created by this class.
355
+ # Running this when you're deployed version of your application is steadily
356
+ # and consistently using the latest code, is the way to clear out old design
357
+ # docs. Running it to early could mean that live code has to regenerate
358
+ # potentially large indexes.
359
+ def cleanup_design_docs!
360
+ ddocs = all_design_doc_versions
361
+ ddocs["rows"].each do |row|
362
+ if (row['id'] != design_doc_id)
363
+ database.delete({
364
+ "_id" => row['id'],
365
+ "_rev" => row['value']['rev']
366
+ })
367
+ end
368
+ end
369
+ end
370
+
371
+ private
372
+
373
+ def fetch_view_with_docs name, opts, raw=false, &block
374
+ if raw
375
+ fetch_view name, opts, &block
376
+ else
377
+ begin
378
+ view = fetch_view name, opts.merge({:include_docs => true}), &block
379
+ view['rows'].collect{|r|new(r['doc'])} if view['rows']
380
+ rescue
381
+ # fallback for old versions of couchdb that don't
382
+ # have include_docs support
383
+ view = fetch_view name, opts, &block
384
+ view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
385
+ end
386
+ end
387
+ end
388
+
389
+ def fetch_view view_name, opts, &block
390
+ retryable = true
391
+ begin
392
+ design_doc.view(view_name, opts, &block)
393
+ # the design doc could have been deleted by a rouge process
394
+ rescue RestClient::ResourceNotFound => e
395
+ if retryable
396
+ refresh_design_doc
397
+ retryable = false
398
+ retry
399
+ else
400
+ raise e
401
+ end
402
+ end
403
+ end
404
+
405
+ def design_doc_id
406
+ "_design/#{design_doc_slug}"
407
+ end
408
+
409
+ def design_doc_slug
410
+ return design_doc_slug_cache if design_doc_slug_cache && design_doc_fresh
411
+ funcs = []
412
+ design_doc['views'].each do |name, view|
413
+ funcs << "#{name}/#{view['map']}#{view['reduce']}"
414
+ end
415
+ md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
416
+ self.design_doc_slug_cache = "#{self.to_s}-#{md5}"
417
+ end
418
+
419
+ def default_design_doc
420
+ {
421
+ "language" => "javascript",
422
+ "views" => {
423
+ 'all' => {
424
+ 'map' => "function(doc) {
425
+ if (doc['couchrest-type'] == '#{self.to_s}') {
426
+ emit(null,null);
427
+ }
428
+ }"
429
+ }
430
+ }
431
+ }
432
+ end
433
+
434
+ def refresh_design_doc
435
+ did = design_doc_id
436
+ saved = database.get(did) rescue nil
437
+ if saved
438
+ design_doc['views'].each do |name, view|
439
+ saved['views'][name] = view
440
+ end
441
+ database.save(saved)
442
+ else
443
+ design_doc['_id'] = did
444
+ design_doc.delete('_rev')
445
+ design_doc.database = database
446
+ design_doc.save
447
+ end
448
+ self.design_doc_fresh = true
449
+ end
450
+
451
+ end # class << self
452
+
453
+ # returns the database used by this model's class
454
+ def database
455
+ self.class.database
456
+ end
457
+
458
+ # Takes a hash as argument, and applies the values by using writer methods
459
+ # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
460
+ # missing. In case of error, no attributes are changed.
461
+ def update_attributes_without_saving hash
462
+ hash.each do |k, v|
463
+ raise NoMethodError, "#{k}= method not available, use key_accessor or key_writer :#{k}" unless self.respond_to?("#{k}=")
464
+ end
465
+ hash.each do |k, v|
466
+ self.send("#{k}=",v)
467
+ end
468
+ end
469
+
470
+ # Takes a hash as argument, and applies the values by using writer methods
471
+ # for each key. Raises a NoMethodError if the corresponding methods are
472
+ # missing. In case of error, no attributes are changed.
473
+ def update_attributes hash
474
+ update_attributes_without_saving hash
475
+ save
476
+ end
477
+
478
+ # for compatibility with old-school frameworks
479
+ alias :new_record? :new_document?
480
+
481
+ # Overridden to set the unique ID.
482
+ def save bulk = false
483
+ set_unique_id if new_document? && self.respond_to?(:set_unique_id)
484
+ super(bulk)
485
+ end
486
+
487
+ # Saves the document to the db using create or update. Raises an exception
488
+ # if the document is not saved properly.
489
+ def save!
490
+ raise "#{self.inspect} failed to save" unless self.save
491
+ end
492
+
493
+ # Deletes the document from the database. Runs the :destroy callbacks.
494
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
495
+ # document to be saved to a new <tt>_id</tt>.
496
+ def destroy
497
+ result = database.delete self
498
+ if result['ok']
499
+ self['_rev'] = nil
500
+ self['_id'] = nil
501
+ end
502
+ result['ok']
503
+ end
504
+
505
+ # creates a file attachment to the current doc
506
+ def create_attachment(args={})
507
+ raise ArgumentError unless args[:file] && args[:name]
508
+ return if has_attachment?(args[:name])
509
+ self['_attachments'] ||= {}
510
+ set_attachment_attr(args)
511
+ rescue ArgumentError => e
512
+ raise ArgumentError, 'You must specify :file and :name'
513
+ end
514
+
515
+ # reads the data from an attachment
516
+ def read_attachment(attachment_name)
517
+ Base64.decode64(database.fetch_attachment(self.id, attachment_name))
518
+ end
519
+
520
+ # modifies a file attachment on the current doc
521
+ def update_attachment(args={})
522
+ raise ArgumentError unless args[:file] && args[:name]
523
+ return unless has_attachment?(args[:name])
524
+ delete_attachment(args[:name])
525
+ set_attachment_attr(args)
526
+ rescue ArgumentError => e
527
+ raise ArgumentError, 'You must specify :file and :name'
528
+ end
529
+
530
+ # deletes a file attachment from the current doc
531
+ def delete_attachment(attachment_name)
532
+ return unless self['_attachments']
533
+ self['_attachments'].delete attachment_name
534
+ end
535
+
536
+ # returns true if attachment_name exists
537
+ def has_attachment?(attachment_name)
538
+ !!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
539
+ end
540
+
541
+ # returns URL to fetch the attachment from
542
+ def attachment_url(attachment_name)
543
+ return unless has_attachment?(attachment_name)
544
+ "#{database.root}/#{self.id}/#{attachment_name}"
545
+ end
546
+
547
+ private
548
+
549
+ def apply_defaults
550
+ return unless new_document?
551
+ if self.class.default
552
+ self.class.default.each do |k,v|
553
+ unless self.key?(k.to_s)
554
+ if v.class == Proc
555
+ self[k.to_s] = v.call
556
+ else
557
+ self[k.to_s] = Marshal.load(Marshal.dump(v))
558
+ end
559
+ end
560
+ end
561
+ end
562
+ end
563
+
564
+ def cast_keys
565
+ return unless self.class.casts
566
+ # TODO move the argument checking to the cast method for early crashes
567
+ self.class.casts.each do |k,v|
568
+ next unless self[k]
569
+ target = v[:as]
570
+ v[:send] || 'new'
571
+ if target.is_a?(Array)
572
+ klass = ::Extlib::Inflection.constantize(target[0])
573
+ self[k] = self[k].collect do |value|
574
+ (!v[:send] && klass == Time) ? Time.parse(value) : klass.send((v[:send] || 'new'), value)
575
+ end
576
+ else
577
+ self[k] = if (!v[:send] && target == 'Time')
578
+ Time.parse(self[k])
579
+ else
580
+ ::Extlib::Inflection.constantize(target).send((v[:send] || 'new'), self[k])
581
+ end
582
+ end
583
+ end
584
+ end
585
+
586
+ def encode_attachment(data)
587
+ Base64.encode64(data).gsub(/\r|\n/,'')
588
+ end
589
+
590
+ def get_mime_type(file)
591
+ MIME::Types.type_for(file.path).empty? ?
592
+ 'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
593
+ end
594
+
595
+ def set_attachment_attr(args)
596
+ content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
597
+ self['_attachments'][args[:name]] = {
598
+ 'content-type' => content_type,
599
+ 'data' => encode_attachment(args[:file].read)
600
+ }
601
+ end
602
+
603
+ include ::Extlib::Hook
604
+ register_instance_hooks :save, :destroy
605
+
606
+ end # class Model
607
+ end # module CouchRest
@@ -0,0 +1,51 @@
1
+ module CouchRest
2
+ class Server
3
+ attr_accessor :uri, :uuid_batch_count
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
+ # List all databases on the server
10
+ def databases
11
+ CouchRest.get "#{@uri}/_all_dbs"
12
+ end
13
+
14
+ # Returns a CouchRest::Database for the given name
15
+ def database name
16
+ CouchRest::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
+ CouchRest.get "#{@uri}/"
28
+ end
29
+
30
+ # Create a database
31
+ def create_db name
32
+ CouchRest.put "#{@uri}/#{name}"
33
+ database name
34
+ end
35
+
36
+ # Restart the CouchDB instance
37
+ def restart!
38
+ CouchRest.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 = CouchRest.post("#{@uri}/_uuids?count=#{count}")["uuids"]
46
+ end
47
+ @uuids.pop
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ module CouchRest
2
+ class View
3
+ end
4
+ end