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.
- data/{README.rdoc → README.md} +10 -8
- data/Rakefile +60 -39
- data/THANKS.md +18 -0
- data/examples/word_count/markov +1 -1
- data/examples/word_count/views/word_count/count-reduce.js +2 -2
- data/examples/word_count/word_count.rb +2 -2
- data/examples/word_count/word_count_query.rb +2 -2
- data/lib/couchrest/commands/push.rb +8 -4
- data/lib/couchrest/core/database.rb +95 -14
- data/lib/couchrest/core/design.rb +89 -0
- data/lib/couchrest/core/document.rb +63 -0
- data/lib/couchrest/core/model.rb +203 -120
- data/lib/couchrest/core/server.rb +1 -1
- data/lib/couchrest/core/view.rb +4 -0
- data/lib/couchrest/helper/pager.rb +10 -10
- data/lib/couchrest/monkeypatches.rb +1 -1
- data/lib/couchrest.rb +20 -2
- data/spec/couchrest/core/couchrest_spec.rb +33 -23
- data/spec/couchrest/core/database_spec.rb +163 -8
- data/spec/couchrest/core/design_spec.rb +131 -0
- data/spec/couchrest/core/document_spec.rb +130 -0
- data/spec/couchrest/core/model_spec.rb +319 -35
- data/spec/couchrest/helpers/pager_spec.rb +2 -2
- data/spec/fixtures/attachments/README +3 -0
- data/spec/spec_helper.rb +17 -3
- data/utils/remap.rb +2 -2
- data/utils/subset.rb +2 -2
- metadata +24 -33
- data/THANKS +0 -15
- data/bin/couchapp +0 -55
- data/bin/couchview +0 -48
- data/lib/couchrest/helper/file_manager.rb +0 -285
- data/lib/couchrest/helper/templates/example-map.js +0 -8
- data/lib/couchrest/helper/templates/example-reduce.js +0 -10
- data/lib/couchrest/helper/templates/index.html +0 -26
- data/spec/couchapp_spec.rb +0 -82
- data/spec/couchrest/helpers/file_manager_spec.rb +0 -170
data/lib/couchrest/core/model.rb
CHANGED
@@ -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 -
|
7
|
+
# = CouchRest::Model - Document modeling, the CouchDB way
|
6
8
|
module CouchRest
|
7
|
-
# = CouchRest::Model -
|
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://
|
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 :
|
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 <
|
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 :
|
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
|
114
|
+
# name of the current class. Take the standard set of
|
116
115
|
# CouchRest::Database#view options.
|
117
|
-
def all opts = {}
|
118
|
-
self.
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|
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(:
|
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
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
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
|
325
|
+
if has_view?(m)
|
307
326
|
query = args.shift || {}
|
308
|
-
view(m,
|
327
|
+
view(m, query, *args)
|
309
328
|
else
|
310
329
|
super
|
311
330
|
end
|
312
331
|
end
|
313
332
|
|
314
|
-
# returns
|
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
|
-
|
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
|
-
|
335
|
-
|
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
|
-
|
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
|
-
|
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 =
|
435
|
+
did = design_doc_id
|
399
436
|
saved = database.get(did) rescue nil
|
400
437
|
if saved
|
401
|
-
|
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
|
-
|
407
|
-
|
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
|
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 :#{
|
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
|
-
#
|
443
|
-
|
444
|
-
|
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
|
-
#
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
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 :
|
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
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
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
|
-
#
|
480
|
-
def
|
481
|
-
|
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
|
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.
|
574
|
+
(!v[:send] && klass == Time) ? Time.parse(value) : klass.send((v[:send] || 'new'), value)
|
513
575
|
end
|
514
576
|
else
|
515
|
-
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, :
|
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://
|
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
|
@@ -5,13 +5,13 @@ module CouchRest
|
|
5
5
|
@db = db
|
6
6
|
end
|
7
7
|
|
8
|
-
def all_docs(
|
8
|
+
def all_docs(limit=100, &block)
|
9
9
|
startkey = nil
|
10
10
|
oldend = nil
|
11
11
|
|
12
|
-
while docrows = request_all_docs(
|
12
|
+
while docrows = request_all_docs(limit+1, startkey)
|
13
13
|
startkey = docrows.last['key']
|
14
|
-
docrows.pop if docrows.length >
|
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,
|
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,
|
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, :
|
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
|
82
|
+
def request_all_docs limit, startkey = nil
|
83
83
|
opts = {}
|
84
|
-
opts[:
|
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,
|
91
|
+
def request_view view, limit = nil, startkey = nil, endkey = nil
|
92
92
|
opts = {}
|
93
|
-
opts[:
|
93
|
+
opts[:limit] = limit if limit
|
94
94
|
opts[:startkey] = startkey if startkey
|
95
95
|
opts[:endkey] = endkey if endkey
|
96
96
|
|
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 || "
|
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?
|