couchrest 0.12.2

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