jchris-couchrest 0.9.12 → 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 (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?