samlown-couchrest 0.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +46 -0
  3. data/Rakefile +67 -0
  4. data/THANKS.md +19 -0
  5. data/examples/model/example.rb +144 -0
  6. data/examples/word_count/markov +38 -0
  7. data/examples/word_count/views/books/chunked-map.js +3 -0
  8. data/examples/word_count/views/books/united-map.js +1 -0
  9. data/examples/word_count/views/markov/chain-map.js +6 -0
  10. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  11. data/examples/word_count/views/word_count/count-map.js +6 -0
  12. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  13. data/examples/word_count/word_count.rb +46 -0
  14. data/examples/word_count/word_count_query.rb +40 -0
  15. data/examples/word_count/word_count_views.rb +26 -0
  16. data/history.txt +114 -0
  17. data/lib/couchrest/commands/generate.rb +71 -0
  18. data/lib/couchrest/commands/push.rb +103 -0
  19. data/lib/couchrest/core/adapters/restclient.rb +35 -0
  20. data/lib/couchrest/core/database.rb +377 -0
  21. data/lib/couchrest/core/design.rb +79 -0
  22. data/lib/couchrest/core/document.rb +84 -0
  23. data/lib/couchrest/core/http_abstraction.rb +48 -0
  24. data/lib/couchrest/core/response.rb +16 -0
  25. data/lib/couchrest/core/rest_api.rb +49 -0
  26. data/lib/couchrest/core/server.rb +88 -0
  27. data/lib/couchrest/core/view.rb +4 -0
  28. data/lib/couchrest/helper/pager.rb +103 -0
  29. data/lib/couchrest/helper/streamer.rb +51 -0
  30. data/lib/couchrest/helper/upgrade.rb +51 -0
  31. data/lib/couchrest/middlewares/logger.rb +263 -0
  32. data/lib/couchrest/mixins/attachments.rb +31 -0
  33. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  34. data/lib/couchrest/mixins/callbacks.rb +532 -0
  35. data/lib/couchrest/mixins/class_proxy.rb +124 -0
  36. data/lib/couchrest/mixins/collection.rb +260 -0
  37. data/lib/couchrest/mixins/design_doc.rb +103 -0
  38. data/lib/couchrest/mixins/document_queries.rb +80 -0
  39. data/lib/couchrest/mixins/extended_attachments.rb +70 -0
  40. data/lib/couchrest/mixins/extended_document_mixins.rb +9 -0
  41. data/lib/couchrest/mixins/properties.rb +154 -0
  42. data/lib/couchrest/mixins/validation.rb +246 -0
  43. data/lib/couchrest/mixins/views.rb +173 -0
  44. data/lib/couchrest/mixins.rb +4 -0
  45. data/lib/couchrest/monkeypatches.rb +113 -0
  46. data/lib/couchrest/more/casted_model.rb +58 -0
  47. data/lib/couchrest/more/extended_document.rb +310 -0
  48. data/lib/couchrest/more/property.rb +50 -0
  49. data/lib/couchrest/more/typecast.rb +175 -0
  50. data/lib/couchrest/support/blank.rb +42 -0
  51. data/lib/couchrest/support/class.rb +190 -0
  52. data/lib/couchrest/support/rails.rb +42 -0
  53. data/lib/couchrest/validation/auto_validate.rb +157 -0
  54. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  55. data/lib/couchrest/validation/validation_errors.rb +125 -0
  56. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  57. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  58. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  59. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  60. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  61. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  62. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  63. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  64. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  65. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  66. data/lib/couchrest.rb +162 -0
  67. data/spec/couchrest/core/couchrest_spec.rb +184 -0
  68. data/spec/couchrest/core/database_spec.rb +840 -0
  69. data/spec/couchrest/core/design_spec.rb +138 -0
  70. data/spec/couchrest/core/document_spec.rb +275 -0
  71. data/spec/couchrest/core/server_spec.rb +35 -0
  72. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  73. data/spec/couchrest/helpers/streamer_spec.rb +52 -0
  74. data/spec/couchrest/more/attribute_protection_spec.rb +150 -0
  75. data/spec/couchrest/more/casted_extended_doc_spec.rb +79 -0
  76. data/spec/couchrest/more/casted_model_spec.rb +406 -0
  77. data/spec/couchrest/more/extended_doc_attachment_spec.rb +135 -0
  78. data/spec/couchrest/more/extended_doc_inherited_spec.rb +40 -0
  79. data/spec/couchrest/more/extended_doc_spec.rb +797 -0
  80. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  81. data/spec/couchrest/more/extended_doc_view_spec.rb +456 -0
  82. data/spec/couchrest/more/property_spec.rb +628 -0
  83. data/spec/fixtures/attachments/README +3 -0
  84. data/spec/fixtures/attachments/couchdb.png +0 -0
  85. data/spec/fixtures/attachments/test.html +11 -0
  86. data/spec/fixtures/more/article.rb +35 -0
  87. data/spec/fixtures/more/card.rb +22 -0
  88. data/spec/fixtures/more/cat.rb +20 -0
  89. data/spec/fixtures/more/course.rb +22 -0
  90. data/spec/fixtures/more/event.rb +8 -0
  91. data/spec/fixtures/more/invoice.rb +17 -0
  92. data/spec/fixtures/more/person.rb +9 -0
  93. data/spec/fixtures/more/question.rb +6 -0
  94. data/spec/fixtures/more/service.rb +12 -0
  95. data/spec/fixtures/more/user.rb +22 -0
  96. data/spec/fixtures/views/lib.js +3 -0
  97. data/spec/fixtures/views/test_view/lib.js +3 -0
  98. data/spec/fixtures/views/test_view/only-map.js +4 -0
  99. data/spec/fixtures/views/test_view/test-map.js +3 -0
  100. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  101. data/spec/spec.opts +6 -0
  102. data/spec/spec_helper.rb +49 -0
  103. data/utils/remap.rb +27 -0
  104. data/utils/subset.rb +30 -0
  105. metadata +223 -0
@@ -0,0 +1,79 @@
1
+ module CouchRest
2
+ class Design < Document
3
+ def view_by *keys
4
+ opts = keys.pop if keys.last.is_a?(Hash)
5
+ opts ||= {}
6
+ self['views'] ||= {}
7
+ method_name = "by_#{keys.join('_and_')}"
8
+
9
+ if opts[:map]
10
+ view = {}
11
+ view['map'] = opts.delete(:map)
12
+ if opts[:reduce]
13
+ view['reduce'] = opts.delete(:reduce)
14
+ opts[:reduce] = false
15
+ end
16
+ self['views'][method_name] = view
17
+ else
18
+ doc_keys = keys.collect{|k|"doc['#{k}']"} # this is where :require => 'doc.x == true' would show up
19
+ key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
20
+ guards = opts.delete(:guards) || []
21
+ guards.concat doc_keys
22
+ map_function = <<-JAVASCRIPT
23
+ function(doc) {
24
+ if (#{guards.join(' && ')}) {
25
+ emit(#{key_emit}, null);
26
+ }
27
+ }
28
+ JAVASCRIPT
29
+ self['views'][method_name] = {
30
+ 'map' => map_function
31
+ }
32
+ end
33
+ self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
34
+ method_name
35
+ end
36
+
37
+ # Dispatches to any named view.
38
+ # (using the database where this design doc was saved)
39
+ def view view_name, query={}, &block
40
+ view_on database, view_name, query, &block
41
+ end
42
+
43
+ # Dispatches to any named view in a specific database
44
+ def view_on db, view_name, query={}, &block
45
+ view_name = view_name.to_s
46
+ view_slug = "#{name}/#{view_name}"
47
+ defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
48
+ db.view(view_slug, defaults.merge(query), &block)
49
+ end
50
+
51
+ def name
52
+ id.sub('_design/','') if id
53
+ end
54
+
55
+ def name= newname
56
+ self['_id'] = "_design/#{newname}"
57
+ end
58
+
59
+ def save
60
+ raise ArgumentError, "_design docs require a name" unless name && name.length > 0
61
+ super
62
+ end
63
+
64
+ private
65
+
66
+ # returns stored defaults if the there is a view named this in the design doc
67
+ def has_view?(view)
68
+ view = view.to_s
69
+ self['views'][view] &&
70
+ (self['views'][view]["couchrest-defaults"]||{})
71
+ end
72
+
73
+ def fetch_view view_name, opts, &block
74
+ database.view(view_name, opts, &block)
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,84 @@
1
+ require 'delegate'
2
+
3
+ module CouchRest
4
+ class Document < Response
5
+ include CouchRest::Mixins::Attachments
6
+
7
+ extlib_inheritable_accessor :database
8
+ attr_accessor :database
9
+
10
+ # override the CouchRest::Model-wide default_database
11
+ # This is not a thread safe operation, do not change the model
12
+ # database at runtime.
13
+ def self.use_database(db)
14
+ self.database = db
15
+ end
16
+
17
+ def id
18
+ self['_id']
19
+ end
20
+
21
+ def rev
22
+ self['_rev']
23
+ end
24
+
25
+ # returns true if the document has never been saved
26
+ def new?
27
+ !rev
28
+ end
29
+ alias :new_document? :new?
30
+
31
+ # Saves the document to the db using create or update. Also runs the :save
32
+ # callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
33
+ # CouchDB's response.
34
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
35
+ def save(bulk = false)
36
+ raise ArgumentError, "doc.database required for saving" unless database
37
+ result = database.save_doc self, bulk
38
+ result['ok']
39
+ end
40
+
41
+ # Deletes the document from the database. Runs the :delete callbacks.
42
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
43
+ # document to be saved to a new <tt>_id</tt>.
44
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
45
+ # actually be deleted from the db until bulk save.
46
+ def destroy(bulk = false)
47
+ raise ArgumentError, "doc.database required to destroy" unless database
48
+ result = database.delete_doc(self, bulk)
49
+ if result['ok']
50
+ self['_rev'] = nil
51
+ self['_id'] = nil
52
+ end
53
+ result['ok']
54
+ end
55
+
56
+ # copies the document to a new id. If the destination id currently exists, a rev must be provided.
57
+ # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
58
+ # hash with a '_rev' key
59
+ def copy(dest)
60
+ raise ArgumentError, "doc.database required to copy" unless database
61
+ result = database.copy_doc(self, dest)
62
+ result['ok']
63
+ end
64
+
65
+ # Returns the CouchDB uri for the document
66
+ def uri(append_rev = false)
67
+ return nil if new?
68
+ couch_uri = "#{database.root}/#{CGI.escape(id)}"
69
+ if append_rev == true
70
+ couch_uri << "?rev=#{rev}"
71
+ elsif append_rev.kind_of?(Integer)
72
+ couch_uri << "?rev=#{append_rev}"
73
+ end
74
+ couch_uri
75
+ end
76
+
77
+ # Returns the document's database
78
+ def database
79
+ @database || self.class.database
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,48 @@
1
+ require 'couchrest/core/adapters/restclient'
2
+
3
+ # Abstraction layet for HTTP communications.
4
+ #
5
+ # By defining a basic API that CouchRest is relying on,
6
+ # it allows for easy experimentations and implementations of various libraries.
7
+ #
8
+ # Most of the API is based on the RestClient API that was used in the early version of CouchRest.
9
+ #
10
+ module HttpAbstraction
11
+
12
+ # here is the list of exception expected by CouchRest
13
+ # please convert the underlying errors in this set of known
14
+ # exceptions.
15
+ class ResourceNotFound < StandardError; end
16
+ class RequestFailed < StandardError; end
17
+ class RequestTimeout < StandardError; end
18
+ class ServerBrokeConnection < StandardError; end
19
+ class Conflict < StandardError; end
20
+
21
+
22
+ # # Here is the API you need to implement if you want to write a new adapter
23
+ # # See adapters/restclient.rb for more information.
24
+ #
25
+ # def self.proxy=(url)
26
+ # end
27
+ #
28
+ # def self.proxy
29
+ # end
30
+ #
31
+ # def self.get(uri, headers=nil)
32
+ # end
33
+ #
34
+ # def self.post(uri, payload, headers=nil)
35
+ # end
36
+ #
37
+ # def self.put(uri, payload, headers=nil)
38
+ # end
39
+ #
40
+ # def self.delete(uri, headers=nil)
41
+ # end
42
+ #
43
+ # def self.copy(uri, headers)
44
+ # end
45
+
46
+ end
47
+
48
+ HttpAbstraction.extend(RestClientAdapter::API)
@@ -0,0 +1,16 @@
1
+ module CouchRest
2
+ class Response < Hash
3
+ def initialize(pkeys = {})
4
+ pkeys ||= {}
5
+ pkeys.each do |k,v|
6
+ self[k.to_s] = v
7
+ end
8
+ end
9
+ def []=(key, value)
10
+ super(key.to_s, value)
11
+ end
12
+ def [](key)
13
+ super(key.to_s)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,49 @@
1
+ module RestAPI
2
+
3
+ def put(uri, doc = nil)
4
+ payload = doc.to_json if doc
5
+ begin
6
+ JSON.parse(HttpAbstraction.put(uri, payload))
7
+ rescue Exception => e
8
+ if $DEBUG
9
+ raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
10
+ else
11
+ raise e
12
+ end
13
+ end
14
+ end
15
+
16
+ def get(uri)
17
+ begin
18
+ JSON.parse(HttpAbstraction.get(uri), :max_nesting => false)
19
+ rescue => e
20
+ if $DEBUG
21
+ raise "Error while sending a GET request #{uri}\n: #{e}"
22
+ else
23
+ raise e
24
+ end
25
+ end
26
+ end
27
+
28
+ def post(uri, doc = nil)
29
+ payload = doc.to_json if doc
30
+ begin
31
+ JSON.parse(HttpAbstraction.post(uri, payload))
32
+ rescue Exception => e
33
+ if $DEBUG
34
+ raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
35
+ else
36
+ raise e
37
+ end
38
+ end
39
+ end
40
+
41
+ def delete(uri)
42
+ JSON.parse(HttpAbstraction.delete(uri))
43
+ end
44
+
45
+ def copy(uri, destination)
46
+ JSON.parse(HttpAbstraction.copy(uri, {'Destination' => destination}))
47
+ end
48
+
49
+ end
@@ -0,0 +1,88 @@
1
+ module CouchRest
2
+ class Server
3
+ attr_accessor :uri, :uuid_batch_count, :available_databases
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
+ # Lists all "available" databases.
10
+ # An available database, is a database that was specified
11
+ # as avaiable by your code.
12
+ # It allows to define common databases to use and reuse in your code
13
+ def available_databases
14
+ @available_databases ||= {}
15
+ end
16
+
17
+ # Adds a new available database and create it unless it already exists
18
+ #
19
+ # Example:
20
+ #
21
+ # @couch = CouchRest::Server.new
22
+ # @couch.define_available_database(:default, "tech-blog")
23
+ #
24
+ def define_available_database(reference, db_name, create_unless_exists = true)
25
+ available_databases[reference.to_sym] = create_unless_exists ? database!(db_name) : database(db_name)
26
+ end
27
+
28
+ # Checks that a database is set as available
29
+ #
30
+ # Example:
31
+ #
32
+ # @couch.available_database?(:default)
33
+ #
34
+ def available_database?(ref_or_name)
35
+ ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(ref_or_name)
36
+ end
37
+
38
+ def default_database=(name, create_unless_exists = true)
39
+ define_available_database(:default, name, create_unless_exists = true)
40
+ end
41
+
42
+ def default_database
43
+ available_databases[:default]
44
+ end
45
+
46
+ # Lists all databases on the server
47
+ def databases
48
+ CouchRest.get "#{@uri}/_all_dbs"
49
+ end
50
+
51
+ # Returns a CouchRest::Database for the given name
52
+ def database(name)
53
+ CouchRest::Database.new(self, name)
54
+ end
55
+
56
+ # Creates the database if it doesn't exist
57
+ def database!(name)
58
+ create_db(name) rescue nil
59
+ database(name)
60
+ end
61
+
62
+ # GET the welcome message
63
+ def info
64
+ CouchRest.get "#{@uri}/"
65
+ end
66
+
67
+ # Create a database
68
+ def create_db(name)
69
+ CouchRest.put "#{@uri}/#{name}"
70
+ database(name)
71
+ end
72
+
73
+ # Restart the CouchDB instance
74
+ def restart!
75
+ CouchRest.post "#{@uri}/_restart"
76
+ end
77
+
78
+ # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
79
+ def next_uuid(count = @uuid_batch_count)
80
+ @uuids ||= []
81
+ if @uuids.empty?
82
+ @uuids = CouchRest.get("#{@uri}/_uuids?count=#{count}")["uuids"]
83
+ end
84
+ @uuids.pop
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,4 @@
1
+ module CouchRest
2
+ class View
3
+ end
4
+ end
@@ -0,0 +1,103 @@
1
+ module CouchRest
2
+ class Pager
3
+ attr_accessor :db
4
+ def initialize db
5
+ @db = db
6
+ end
7
+
8
+ def all_docs(limit=100, &block)
9
+ startkey = nil
10
+ oldend = nil
11
+
12
+ while docrows = request_all_docs(limit+1, startkey)
13
+ startkey = docrows.last['key']
14
+ docrows.pop if docrows.length > limit
15
+ if oldend == startkey
16
+ break
17
+ end
18
+ yield(docrows)
19
+ oldend = startkey
20
+ end
21
+ end
22
+
23
+ def key_reduce(view, limit=2000, firstkey = nil, lastkey = nil, &block)
24
+ # start with no keys
25
+ startkey = firstkey
26
+ # lastprocessedkey = nil
27
+ keepgoing = true
28
+
29
+ while keepgoing && viewrows = request_view(view, limit, startkey)
30
+ startkey = viewrows.first['key']
31
+ endkey = viewrows.last['key']
32
+
33
+ if (startkey == endkey)
34
+ # we need to rerequest to get a bigger page
35
+ # so we know we have all the rows for that key
36
+ viewrows = @db.view(view, :key => startkey)['rows']
37
+ # we need to do an offset thing to find the next startkey
38
+ # otherwise we just get stuck
39
+ lastdocid = viewrows.last['id']
40
+ fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :limit => 2)['rows']
41
+
42
+ newendkey = fornextloop.last['key']
43
+ if (newendkey == endkey)
44
+ keepgoing = false
45
+ else
46
+ startkey = newendkey
47
+ end
48
+ rows = viewrows
49
+ else
50
+ rows = []
51
+ for r in viewrows
52
+ if (lastkey && r['key'] == lastkey)
53
+ keepgoing = false
54
+ break
55
+ end
56
+ break if (r['key'] == endkey)
57
+ rows << r
58
+ end
59
+ startkey = endkey
60
+ end
61
+
62
+ key = :begin
63
+ values = []
64
+
65
+ rows.each do |r|
66
+ if key != r['key']
67
+ # we're on a new key, yield the old first and then reset
68
+ yield(key, values) if key != :begin
69
+ key = r['key']
70
+ values = []
71
+ end
72
+ # keep accumulating
73
+ values << r['value']
74
+ end
75
+ yield(key, values)
76
+
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def request_all_docs limit, startkey = nil
83
+ opts = {}
84
+ opts[:limit] = limit if limit
85
+ opts[:startkey] = startkey if startkey
86
+ results = @db.documents(opts)
87
+ rows = results['rows']
88
+ rows unless rows.length == 0
89
+ end
90
+
91
+ def request_view view, limit = nil, startkey = nil, endkey = nil
92
+ opts = {}
93
+ opts[:limit] = limit if limit
94
+ opts[:startkey] = startkey if startkey
95
+ opts[:endkey] = endkey if endkey
96
+
97
+ results = @db.view(view, opts)
98
+ rows = results['rows']
99
+ rows unless rows.length == 0
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,51 @@
1
+ module CouchRest
2
+ class Streamer
3
+ attr_accessor :db
4
+ def initialize db
5
+ @db = db
6
+ end
7
+
8
+ # Stream a view, yielding one row at a time. Shells out to <tt>curl</tt> to keep RAM usage low when you have millions of rows.
9
+ def view name, params = nil, &block
10
+ urlst = if /^_/.match(name) then
11
+ "#{@db.root}/#{name}"
12
+ else
13
+ name = name.split('/')
14
+ dname = name.shift
15
+ vname = name.join('/')
16
+ "#{@db.root}/_design/#{dname}/_view/#{vname}"
17
+ end
18
+ url = CouchRest.paramify_url urlst, params
19
+ # puts "stream #{url}"
20
+ first = nil
21
+ IO.popen("curl --silent \"#{url}\"") do |view|
22
+ first = view.gets # discard header
23
+ while line = view.gets
24
+ row = parse_line(line)
25
+ block.call row unless row.nil? # last line "}]" discarded
26
+ end
27
+ end
28
+ parse_first(first)
29
+ end
30
+
31
+ private
32
+
33
+ def parse_line line
34
+ return nil unless line
35
+ if /(\{.*\}),?/.match(line.chomp)
36
+ JSON.parse($1)
37
+ end
38
+ end
39
+
40
+ def parse_first first
41
+ return nil unless first
42
+ parts = first.split(',')
43
+ parts.pop
44
+ line = parts.join(',')
45
+ JSON.parse("#{line}}")
46
+ rescue
47
+ nil
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,51 @@
1
+ module CouchRest
2
+ class Upgrade
3
+ attr_accessor :olddb, :newdb, :dbname
4
+ def initialize dbname, old_couch, new_couch
5
+ @dbname = dbname
6
+ @olddb = old_couch.database dbname
7
+ @newdb = new_couch.database!(dbname)
8
+ @bulk_docs = []
9
+ end
10
+ def clone!
11
+ puts "#{dbname} - #{olddb.info['doc_count']} docs"
12
+ streamer = CouchRest::Streamer.new(olddb)
13
+ streamer.view("_all_docs_by_seq") do |row|
14
+ load_row_docs(row) if row
15
+ maybe_flush_bulks
16
+ end
17
+ flush_bulks!
18
+ end
19
+
20
+ private
21
+
22
+ def maybe_flush_bulks
23
+ flush_bulks! if (@bulk_docs.length > 99)
24
+ end
25
+
26
+ def flush_bulks!
27
+ url = CouchRest.paramify_url "#{@newdb.uri}/_bulk_docs", {:all_or_nothing => true}
28
+ puts "posting #{@bulk_docs.length} bulk docs to #{url}"
29
+ begin
30
+ CouchRest.post url, {:docs => @bulk_docs}
31
+ @bulk_docs = []
32
+ rescue Exception => e
33
+ puts e.response
34
+ raise e
35
+ end
36
+ end
37
+
38
+ def load_row_docs(row)
39
+ results = @olddb.get(row["id"], {:open_revs => "all", :attachments => true})
40
+ results.select{|r|r["ok"]}.each do |r|
41
+ doc = r["ok"]
42
+ if /^_/.match(doc["_id"]) && !/^_design/.match(doc["_id"])
43
+ puts "invalid docid #{doc["_id"]} -- trimming"
44
+ doc["_id"] = doc["_id"].sub('_','')
45
+ end
46
+ doc.delete('_rev')
47
+ @bulk_docs << doc
48
+ end
49
+ end
50
+ end
51
+ end