jchris-couchrest 0.9.12 → 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/{README.rdoc → README.md} +10 -8
  2. data/Rakefile +60 -39
  3. data/THANKS.md +18 -0
  4. data/examples/word_count/markov +1 -1
  5. data/examples/word_count/views/word_count/count-reduce.js +2 -2
  6. data/examples/word_count/word_count.rb +2 -2
  7. data/examples/word_count/word_count_query.rb +2 -2
  8. data/lib/couchrest/commands/push.rb +8 -4
  9. data/lib/couchrest/core/database.rb +95 -14
  10. data/lib/couchrest/core/design.rb +89 -0
  11. data/lib/couchrest/core/document.rb +63 -0
  12. data/lib/couchrest/core/model.rb +203 -120
  13. data/lib/couchrest/core/server.rb +1 -1
  14. data/lib/couchrest/core/view.rb +4 -0
  15. data/lib/couchrest/helper/pager.rb +10 -10
  16. data/lib/couchrest/monkeypatches.rb +1 -1
  17. data/lib/couchrest.rb +20 -2
  18. data/spec/couchrest/core/couchrest_spec.rb +33 -23
  19. data/spec/couchrest/core/database_spec.rb +163 -8
  20. data/spec/couchrest/core/design_spec.rb +131 -0
  21. data/spec/couchrest/core/document_spec.rb +130 -0
  22. data/spec/couchrest/core/model_spec.rb +319 -35
  23. data/spec/couchrest/helpers/pager_spec.rb +2 -2
  24. data/spec/fixtures/attachments/README +3 -0
  25. data/spec/spec_helper.rb +17 -3
  26. data/utils/remap.rb +2 -2
  27. data/utils/subset.rb +2 -2
  28. metadata +24 -33
  29. data/THANKS +0 -15
  30. data/bin/couchapp +0 -55
  31. data/bin/couchview +0 -48
  32. data/lib/couchrest/helper/file_manager.rb +0 -285
  33. data/lib/couchrest/helper/templates/example-map.js +0 -8
  34. data/lib/couchrest/helper/templates/example-reduce.js +0 -10
  35. data/lib/couchrest/helper/templates/index.html +0 -26
  36. data/spec/couchapp_spec.rb +0 -82
  37. data/spec/couchrest/helpers/file_manager_spec.rb +0 -170
@@ -1,10 +1,12 @@
1
1
  require 'rubygems'
2
2
  require 'extlib'
3
3
  require 'digest/md5'
4
+ require File.dirname(__FILE__) + '/document'
5
+ require 'mime/types'
4
6
 
5
- # = CouchRest::Model - ORM, the CouchDB way
7
+ # = CouchRest::Model - Document modeling, the CouchDB way
6
8
  module CouchRest
7
- # = CouchRest::Model - ORM, the CouchDB way
9
+ # = CouchRest::Model - Document modeling, the CouchDB way
8
10
  #
9
11
  # CouchRest::Model provides an ORM-like interface for CouchDB documents. It
10
12
  # avoids all usage of <tt>method_missing</tt>, and tries to strike a balance
@@ -18,7 +20,7 @@ module CouchRest
18
20
  # than this example.
19
21
  #
20
22
  # class Article < CouchRest::Model
21
- # use_database CouchRest.database!('http://localhost:5984/couchrest-model-test')
23
+ # use_database CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
22
24
  # unique_id :slug
23
25
  #
24
26
  # view_by :date, :descending => true
@@ -61,22 +63,19 @@ module CouchRest
61
63
  # * The most recent 20 articles. Remember that the <tt>view_by :date</tt>
62
64
  # has the default option <tt>:descending => true</tt>.
63
65
  #
64
- # Article.by_date :count => 20
66
+ # Article.by_date :limit => 20
65
67
  #
66
68
  # * The raw CouchDB view reduce result for the custom <tt>:tags</tt> view.
67
69
  # In this case we'll get a count of the number of articles tagged "ruby".
68
70
  #
69
71
  # Article.by_tags :key => "ruby", :reduce => true
70
72
  #
71
- class Model < Hash
73
+ class Model < Document
72
74
 
73
75
  # instantiates the hash by converting all the keys to strings.
74
76
  def initialize keys = {}
75
- super()
77
+ super(keys)
76
78
  apply_defaults
77
- keys.each do |k,v|
78
- self[k.to_s] = v
79
- end
80
79
  cast_keys
81
80
  unless self['_id'] && self['_rev']
82
81
  self['couchrest-type'] = self.class.to_s
@@ -90,7 +89,7 @@ module CouchRest
90
89
  class_inheritable_accessor :casts
91
90
  class_inheritable_accessor :default_obj
92
91
  class_inheritable_accessor :class_database
93
- class_inheritable_accessor :generated_design_doc
92
+ class_inheritable_accessor :design_doc
94
93
  class_inheritable_accessor :design_doc_slug_cache
95
94
  class_inheritable_accessor :design_doc_fresh
96
95
 
@@ -112,23 +111,66 @@ module CouchRest
112
111
  end
113
112
 
114
113
  # Load all documents that have the "couchrest-type" field equal to the
115
- # name of the current class. Take thes the standard set of
114
+ # name of the current class. Take the standard set of
116
115
  # CouchRest::Database#view options.
117
- def all opts = {}
118
- self.generated_design_doc ||= default_design_doc
116
+ def all opts = {}, &block
117
+ self.design_doc ||= Design.new(default_design_doc)
119
118
  unless design_doc_fresh
120
119
  refresh_design_doc
121
120
  end
122
- view_name = "#{design_doc_slug}/all"
123
- raw = opts.delete(:raw)
124
- fetch_view_with_docs(view_name, opts, raw)
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
125
138
  end
126
139
 
127
140
  # Cast a field as another class. The class must be happy to have the
128
- # field's primitive type as the argument to it's constucture. Classes
141
+ # field's primitive type as the argument to it's constuctur. Classes
129
142
  # which inherit from CouchRest::Model are happy to act as sub-objects
130
143
  # for any fields that are stored in JSON as object (and therefore are
131
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
+ #
132
174
  def cast field, opts = {}
133
175
  self.casts ||= {}
134
176
  self.casts[field.to_s] = opts
@@ -175,11 +217,9 @@ module CouchRest
175
217
  # on the document whenever saving occurs. CouchRest uses a pretty
176
218
  # decent time format by default. See Time#to_json
177
219
  def timestamps!
178
- before(:create) do
179
- self['updated_at'] = self['created_at'] = Time.now
180
- end
181
- before(:update) do
220
+ before(:save) do
182
221
  self['updated_at'] = Time.now
222
+ self['created_at'] = self['updated_at'] if new_document?
183
223
  end
184
224
  end
185
225
 
@@ -266,62 +306,34 @@ module CouchRest
266
306
  # To understand the capabilities of this view system more compeletly,
267
307
  # it is recommended that you read the RSpec file at
268
308
  # <tt>spec/core/model_spec.rb</tt>.
309
+
269
310
  def view_by *keys
311
+ self.design_doc ||= Design.new(default_design_doc)
270
312
  opts = keys.pop if keys.last.is_a?(Hash)
271
313
  opts ||= {}
272
- type = self.to_s
273
-
274
- method_name = "by_#{keys.join('_and_')}"
275
- self.generated_design_doc ||= default_design_doc
276
314
  ducktype = opts.delete(:ducktype)
277
- if opts[:map]
278
- view = {}
279
- view['map'] = opts.delete(:map)
280
- if opts[:reduce]
281
- view['reduce'] = opts.delete(:reduce)
282
- opts[:reduce] = false
283
- end
284
- generated_design_doc['views'][method_name] = view
285
- else
286
- doc_keys = keys.collect{|k|"doc['#{k}']"}
287
- key_protection = doc_keys.join(' && ')
288
- key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
289
- map_function = <<-JAVASCRIPT
290
- function(doc) {
291
- if (#{!ducktype ? "doc['couchrest-type'] == '#{type}' && " : ""}#{key_protection}) {
292
- emit(#{key_emit}, null);
293
- }
294
- }
295
- JAVASCRIPT
296
- generated_design_doc['views'][method_name] = {
297
- 'map' => map_function
298
- }
315
+ unless ducktype || opts[:map]
316
+ opts[:guards] ||= []
317
+ opts[:guards].push "(doc['couchrest-type'] == '#{self.to_s}')"
299
318
  end
300
- generated_design_doc['views'][method_name]['couchrest-defaults'] = opts
319
+ keys.push opts
320
+ self.design_doc.view_by(*keys)
301
321
  self.design_doc_fresh = false
302
- method_name
303
322
  end
304
323
 
305
324
  def method_missing m, *args
306
- if opts = has_view?(m)
325
+ if has_view?(m)
307
326
  query = args.shift || {}
308
- view(m, opts.merge(query), *args)
327
+ view(m, query, *args)
309
328
  else
310
329
  super
311
330
  end
312
331
  end
313
332
 
314
- # returns true if the there is a view named this in the design doc
333
+ # returns stored defaults if the there is a view named this in the design doc
315
334
  def has_view?(view)
316
335
  view = view.to_s
317
- if generated_design_doc['views'][view]
318
- generated_design_doc['views'][view]["couchrest-defaults"]
319
- end
320
- end
321
-
322
- # Fetch the generated design doc. Could raise an error if the generated views have not been queried yet.
323
- def design_doc
324
- database.get("_design/#{design_doc_slug}")
336
+ design_doc && design_doc['views'] && design_doc['views'][view]
325
337
  end
326
338
 
327
339
  # Dispatches to any named view.
@@ -331,8 +343,29 @@ module CouchRest
331
343
  end
332
344
  query[:raw] = true if query[:reduce]
333
345
  raw = query.delete(:raw)
334
- view_name = "#{design_doc_slug}/#{name}"
335
- fetch_view_with_docs(view_name, query, raw, &block)
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
336
369
  end
337
370
 
338
371
  private
@@ -356,7 +389,7 @@ module CouchRest
356
389
  def fetch_view view_name, opts, &block
357
390
  retryable = true
358
391
  begin
359
- database.view(view_name, opts, &block)
392
+ design_doc.view(view_name, opts, &block)
360
393
  # the design doc could have been deleted by a rouge process
361
394
  rescue RestClient::ResourceNotFound => e
362
395
  if retryable
@@ -369,10 +402,14 @@ module CouchRest
369
402
  end
370
403
  end
371
404
 
405
+ def design_doc_id
406
+ "_design/#{design_doc_slug}"
407
+ end
408
+
372
409
  def design_doc_slug
373
410
  return design_doc_slug_cache if design_doc_slug_cache && design_doc_fresh
374
411
  funcs = []
375
- generated_design_doc['views'].each do |name, view|
412
+ design_doc['views'].each do |name, view|
376
413
  funcs << "#{name}/#{view['map']}#{view['reduce']}"
377
414
  end
378
415
  md5 = Digest::MD5.hexdigest(funcs.sort.join(''))
@@ -395,16 +432,18 @@ module CouchRest
395
432
  end
396
433
 
397
434
  def refresh_design_doc
398
- did = "_design/#{design_doc_slug}"
435
+ did = design_doc_id
399
436
  saved = database.get(did) rescue nil
400
437
  if saved
401
- generated_design_doc['views'].each do |name, view|
438
+ design_doc['views'].each do |name, view|
402
439
  saved['views'][name] = view
403
440
  end
404
441
  database.save(saved)
405
442
  else
406
- generated_design_doc['_id'] = did
407
- database.save(generated_design_doc)
443
+ design_doc['_id'] = did
444
+ design_doc.delete('_rev')
445
+ design_doc.database = database
446
+ design_doc.save
408
447
  end
409
448
  self.design_doc_fresh = true
410
449
  end
@@ -416,46 +455,42 @@ module CouchRest
416
455
  self.class.database
417
456
  end
418
457
 
419
- # alias for self['_id']
420
- def id
421
- self['_id']
422
- end
423
-
424
- # alias for self['_rev']
425
- def rev
426
- self['_rev']
427
- end
428
-
429
458
  # Takes a hash as argument, and applies the values by using writer methods
430
- # for each key. Raises a NoMethodError if the corresponding methods are
431
- # missing. In case of error, no attributes are changed.
432
- def update_attributes hash
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
433
462
  hash.each do |k, v|
434
- raise NoMethodError, "#{k}= method not available, use key_accessor or key_writer :#{key}" unless self.respond_to?("#{k}=")
463
+ raise NoMethodError, "#{k}= method not available, use key_accessor or key_writer :#{k}" unless self.respond_to?("#{k}=")
435
464
  end
436
465
  hash.each do |k, v|
437
466
  self.send("#{k}=",v)
438
467
  end
439
- save
440
468
  end
441
469
 
442
- # returns true if the document has never been saved
443
- def new_record?
444
- !rev
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
445
476
  end
446
477
 
447
- # Saves the document to the db using create or update. Also runs the :save
448
- # callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
449
- # CouchDB's response.
450
- def save
451
- if new_record?
452
- create
453
- else
454
- update
455
- end
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
456
491
  end
457
492
 
458
- # Deletes the document from the database. Runs the :delete callbacks.
493
+ # Deletes the document from the database. Runs the :destroy callbacks.
459
494
  # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
460
495
  # document to be saved to a new <tt>_id</tt>.
461
496
  def destroy
@@ -467,35 +502,61 @@ module CouchRest
467
502
  result['ok']
468
503
  end
469
504
 
470
- protected
471
-
472
- # Saves a document for the first time, after running the before(:create)
473
- # callbacks, and applying the unique_id.
474
- def create
475
- set_unique_id if respond_to?(:set_unique_id) # hack
476
- save_doc
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?)
477
539
  end
478
540
 
479
- # Saves the document and runs the :update callbacks.
480
- def update
481
- save_doc
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}"
482
545
  end
483
546
 
484
547
  private
485
548
 
486
- def save_doc
487
- result = database.save self
488
- if result['ok']
489
- self['_id'] = result['id']
490
- self['_rev'] = result['rev']
491
- end
492
- result['ok']
493
- end
494
-
495
549
  def apply_defaults
550
+ return unless new_document?
496
551
  if self.class.default
497
552
  self.class.default.each do |k,v|
498
- self[k.to_s] = 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
499
560
  end
500
561
  end
501
562
  end
@@ -506,19 +567,41 @@ module CouchRest
506
567
  self.class.casts.each do |k,v|
507
568
  next unless self[k]
508
569
  target = v[:as]
570
+ v[:send] || 'new'
509
571
  if target.is_a?(Array)
510
572
  klass = ::Extlib::Inflection.constantize(target[0])
511
573
  self[k] = self[k].collect do |value|
512
- klass.new(value)
574
+ (!v[:send] && klass == Time) ? Time.parse(value) : klass.send((v[:send] || 'new'), value)
513
575
  end
514
576
  else
515
- self[k] = ::Extlib::Inflection.constantize(target).new(self[k])
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
516
582
  end
517
583
  end
518
584
  end
519
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
+
520
603
  include ::Extlib::Hook
521
- register_instance_hooks :save, :create, :update, :destroy
604
+ register_instance_hooks :save, :destroy
522
605
 
523
606
  end # class Model
524
- end # module CouchRest
607
+ end # module CouchRest
@@ -1,7 +1,7 @@
1
1
  module CouchRest
2
2
  class Server
3
3
  attr_accessor :uri, :uuid_batch_count
4
- def initialize server = 'http://localhost:5984', uuid_batch_count = 1000
4
+ def initialize server = 'http://127.0.0.1:5984', uuid_batch_count = 1000
5
5
  @uri = server
6
6
  @uuid_batch_count = uuid_batch_count
7
7
  end
@@ -0,0 +1,4 @@
1
+ module CouchRest
2
+ class View
3
+ end
4
+ end
@@ -5,13 +5,13 @@ module CouchRest
5
5
  @db = db
6
6
  end
7
7
 
8
- def all_docs(count=100, &block)
8
+ def all_docs(limit=100, &block)
9
9
  startkey = nil
10
10
  oldend = nil
11
11
 
12
- while docrows = request_all_docs(count+1, startkey)
12
+ while docrows = request_all_docs(limit+1, startkey)
13
13
  startkey = docrows.last['key']
14
- docrows.pop if docrows.length > count
14
+ docrows.pop if docrows.length > limit
15
15
  if oldend == startkey
16
16
  break
17
17
  end
@@ -20,13 +20,13 @@ module CouchRest
20
20
  end
21
21
  end
22
22
 
23
- def key_reduce(view, count, firstkey = nil, lastkey = nil, &block)
23
+ def key_reduce(view, limit=2000, firstkey = nil, lastkey = nil, &block)
24
24
  # start with no keys
25
25
  startkey = firstkey
26
26
  # lastprocessedkey = nil
27
27
  keepgoing = true
28
28
 
29
- while keepgoing && viewrows = request_view(view, count, startkey)
29
+ while keepgoing && viewrows = request_view(view, limit, startkey)
30
30
  startkey = viewrows.first['key']
31
31
  endkey = viewrows.last['key']
32
32
 
@@ -37,7 +37,7 @@ module CouchRest
37
37
  # we need to do an offset thing to find the next startkey
38
38
  # otherwise we just get stuck
39
39
  lastdocid = viewrows.last['id']
40
- fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :count => 2)['rows']
40
+ fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :limit => 2)['rows']
41
41
 
42
42
  newendkey = fornextloop.last['key']
43
43
  if (newendkey == endkey)
@@ -79,18 +79,18 @@ module CouchRest
79
79
 
80
80
  private
81
81
 
82
- def request_all_docs count, startkey = nil
82
+ def request_all_docs limit, startkey = nil
83
83
  opts = {}
84
- opts[:count] = count if count
84
+ opts[:limit] = limit if limit
85
85
  opts[:startkey] = startkey if startkey
86
86
  results = @db.documents(opts)
87
87
  rows = results['rows']
88
88
  rows unless rows.length == 0
89
89
  end
90
90
 
91
- def request_view view, count = nil, startkey = nil, endkey = nil
91
+ def request_view view, limit = nil, startkey = nil, endkey = nil
92
92
  opts = {}
93
- opts[:count] = count if count
93
+ opts[:limit] = limit if limit
94
94
  opts[:startkey] = startkey if startkey
95
95
  opts[:endkey] = endkey if endkey
96
96
 
@@ -8,7 +8,7 @@ class Time
8
8
 
9
9
  def to_json(options = nil)
10
10
  u = self.utc
11
- %("#{u.strftime("%Y/%m/%d %H:%M:%S +0000")}")
11
+ %("#{u.strftime("%Y/%m/%d %H:%M:%S.#{u.usec} +0000")}")
12
12
  end
13
13
 
14
14
  # Decodes the JSON time format to a UTC time.
data/lib/couchrest.rb CHANGED
@@ -15,7 +15,7 @@
15
15
  require "rubygems"
16
16
  require 'json'
17
17
  require 'rest_client'
18
- require 'extlib'
18
+ # require 'extlib'
19
19
 
20
20
  $:.unshift File.dirname(__FILE__) unless
21
21
  $:.include?(File.dirname(__FILE__)) ||
@@ -26,8 +26,13 @@ require 'couchrest/monkeypatches'
26
26
 
27
27
  # = CouchDB, close to the metal
28
28
  module CouchRest
29
+ VERSION = '0.12.2'
30
+
29
31
  autoload :Server, 'couchrest/core/server'
30
32
  autoload :Database, 'couchrest/core/database'
33
+ autoload :Document, 'couchrest/core/document'
34
+ autoload :Design, 'couchrest/core/design'
35
+ autoload :View, 'couchrest/core/view'
31
36
  autoload :Model, 'couchrest/core/model'
32
37
  autoload :Pager, 'couchrest/helper/pager'
33
38
  autoload :FileManager, 'couchrest/helper/file_manager'
@@ -69,12 +74,17 @@ module CouchRest
69
74
  db = nil if db && db.empty?
70
75
 
71
76
  {
72
- :host => host || "localhost:5984",
77
+ :host => host || "127.0.0.1:5984",
73
78
  :database => db,
74
79
  :doc => docid
75
80
  }
76
81
  end
77
82
 
83
+ # set proxy for RestClient to use
84
+ def proxy url
85
+ RestClient.proxy = url
86
+ end
87
+
78
88
  # ensure that a database exists
79
89
  # creates it if it isn't already there
80
90
  # returns it after it's been created
@@ -107,6 +117,14 @@ module CouchRest
107
117
  def delete uri
108
118
  JSON.parse(RestClient.delete(uri))
109
119
  end
120
+
121
+ def copy uri, destination
122
+ JSON.parse(RestClient.copy(uri, {'Destination' => destination}))
123
+ end
124
+
125
+ def move uri, destination
126
+ JSON.parse(RestClient.move(uri, {'Destination' => destination}))
127
+ end
110
128
 
111
129
  def paramify_url url, params = {}
112
130
  if params && !params.empty?