derfred-couchrest 0.12.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +68 -0
  3. data/Rakefile +66 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +138 -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/lib/couchrest/commands/generate.rb +71 -0
  17. data/lib/couchrest/commands/push.rb +103 -0
  18. data/lib/couchrest/core/database.rb +314 -0
  19. data/lib/couchrest/core/design.rb +89 -0
  20. data/lib/couchrest/core/document.rb +101 -0
  21. data/lib/couchrest/core/model.rb +615 -0
  22. data/lib/couchrest/core/server.rb +88 -0
  23. data/lib/couchrest/core/view.rb +4 -0
  24. data/lib/couchrest/helper/pager.rb +103 -0
  25. data/lib/couchrest/helper/streamer.rb +44 -0
  26. data/lib/couchrest/monkeypatches.rb +99 -0
  27. data/lib/couchrest.rb +161 -0
  28. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  29. data/spec/couchrest/core/database_spec.rb +740 -0
  30. data/spec/couchrest/core/design_spec.rb +131 -0
  31. data/spec/couchrest/core/document_spec.rb +311 -0
  32. data/spec/couchrest/core/model_spec.rb +855 -0
  33. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  34. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  35. data/spec/fixtures/attachments/README +3 -0
  36. data/spec/fixtures/attachments/couchdb.png +0 -0
  37. data/spec/fixtures/attachments/test.html +11 -0
  38. data/spec/fixtures/views/lib.js +3 -0
  39. data/spec/fixtures/views/test_view/lib.js +3 -0
  40. data/spec/fixtures/views/test_view/only-map.js +4 -0
  41. data/spec/fixtures/views/test_view/test-map.js +3 -0
  42. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  43. data/spec/spec.opts +6 -0
  44. data/spec/spec_helper.rb +21 -0
  45. data/utils/remap.rb +27 -0
  46. data/utils/subset.rb +30 -0
  47. metadata +143 -0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__)) + '/../../couchrest'
4
+
5
+ cr = CouchRest.new("http://127.0.0.1:5984")
6
+ @db = cr.database('word-count-example')
7
+ @word_memoizer = {}
8
+
9
+ def probable_follower_for(word)
10
+ @word_memoizer[word] ||= @db.view('markov/chain-reduce', :startkey => [word,nil], :endkey => [word,{}],:group_level => 2)
11
+
12
+ # puts
13
+ # puts "search #{word} #{wprobs[word]['rows'].length}"
14
+ # @word_memoizer[word]['rows'].sort_by{|r|r['value']}.each{|r|puts [r['value'],r['key']].inspect}
15
+
16
+ rows = @word_memoizer[word]['rows'].select{|r|(r['key'][1]!='')}.sort_by{|r|r['value']}
17
+ row = rows[(-1*[rows.length,5].min)..-1].sort_by{rand}[0]
18
+ row ? row['key'][1] : nil
19
+ end
20
+
21
+
22
+ word = ARGV[0]
23
+ words = [word]
24
+
25
+ while word
26
+ $stdout.print ' ' if words.length > 1
27
+ $stdout.print word
28
+ $stdout.flush
29
+ word = probable_follower_for(word)
30
+ words << word
31
+ end
32
+
33
+ $stdout.print '.'
34
+ $stdout.flush
35
+ puts
36
+
37
+ # `say #{words.join(' ')}`
38
+
@@ -0,0 +1,3 @@
1
+ function(doc) {
2
+ doc.title && doc.chunk && emit([doc.title, doc.chunk],null);
3
+ }
@@ -0,0 +1 @@
1
+ function(doc){if(doc.text && doc.text.match(/united/)) emit([doc.title, doc.chunk],null)}
@@ -0,0 +1,6 @@
1
+ function(doc){
2
+ var words = doc.text.split(/\W/).filter(function(w) {return w.length > 0}).map(function(w){return w.toLowerCase()});
3
+ for (var i = 0, l = words.length; i < l; i++) {
4
+ emit(words.slice(i,4),doc.title);
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ function(key,vs,c){
2
+ if (c) {
3
+ return sum(vs);
4
+ } else {
5
+ return vs.length;
6
+ }
7
+ }
@@ -0,0 +1,6 @@
1
+ function(doc){
2
+ var words = doc.text.split(/\W/).map(function(w){return w.toLowerCase()});
3
+ words.forEach(function(word){
4
+ if (word.length > 0) emit([word,doc.title],1);
5
+ });
6
+ }
@@ -0,0 +1,3 @@
1
+ function(key,values){
2
+ return sum(values);
3
+ }
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'couchrest'
3
+
4
+ couch = CouchRest.new("http://127.0.0.1:5984")
5
+ db = couch.database('word-count-example')
6
+ db.delete! rescue nil
7
+ db = couch.create_db('word-count-example')
8
+
9
+ books = {
10
+ 'outline-of-science.txt' => 'http://www.gutenberg.org/files/20417/20417.txt',
11
+ 'ulysses.txt' => 'http://www.gutenberg.org/dirs/etext03/ulyss12.txt',
12
+ 'america.txt' => 'http://www.gutenberg.org/files/16960/16960.txt',
13
+ 'da-vinci.txt' => 'http://www.gutenberg.org/dirs/etext04/7ldv110.txt'
14
+ }
15
+
16
+ books.each do |file, url|
17
+ pathfile = File.join(File.dirname(__FILE__),file)
18
+ `curl #{url} > #{pathfile}` unless File.exists?(pathfile)
19
+ end
20
+
21
+
22
+ books.keys.each do |book|
23
+ title = book.split('.')[0]
24
+ puts title
25
+ File.open(File.join(File.dirname(__FILE__),book),'r') do |file|
26
+ lines = []
27
+ chunk = 0
28
+ while line = file.gets
29
+ lines << line
30
+ if lines.length > 10
31
+ db.save({
32
+ :title => title,
33
+ :chunk => chunk,
34
+ :text => lines.join('')
35
+ })
36
+ chunk += 1
37
+ puts chunk
38
+ lines = []
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # puts "The books have been stored in your CouchDB. To initiate the MapReduce process, visit http://127.0.0.1:5984/_utils/ in your browser and click 'word-count-example', then select view 'words' or 'count'. The process could take about 15 minutes on an average MacBook."
45
+
46
+
@@ -0,0 +1,40 @@
1
+ require 'rubygems'
2
+ require 'couchrest'
3
+
4
+ couch = CouchRest.new("http://127.0.0.1:5984")
5
+ db = couch.database('word-count-example')
6
+
7
+ puts "Now that we've parsed all those books into CouchDB, the queries we can run are incredibly flexible."
8
+ puts "\nThe simplest query we can run is the total word count for all words in all documents:"
9
+ puts "this will take a few minutes the first time. if it times out, just rerun this script in a few few minutes."
10
+ puts db.view('word_count/words').inspect
11
+
12
+ puts "\nWe can also narrow the query down to just one word, across all documents. Here is the count for 'flight' in all three books:"
13
+
14
+ word = 'flight'
15
+ params = {
16
+ :startkey => [word],
17
+ :endkey => [word,{}]
18
+ }
19
+
20
+ puts db.view('word_count/words',params).inspect
21
+
22
+ puts "\nWe scope the query using startkey and endkey params to take advantage of CouchDB's collation ordering. Here are the params for the last query:"
23
+ puts params.inspect
24
+
25
+ puts "\nWe can also count words on a per-title basis."
26
+
27
+ title = 'da-vinci'
28
+ params = {
29
+ :key => [word, title]
30
+ }
31
+
32
+ puts db.view('word_count/words',params).inspect
33
+
34
+
35
+ puts "\nHere are the params for 'flight' in the da-vinci book:"
36
+ puts params.inspect
37
+ puts
38
+ puts 'The url looks like this:'
39
+ puts 'http://127.0.0.1:5984/word-count-example/_view/word_count/count?key=["flight","da-vinci"]'
40
+ puts "\nTry dropping that in your browser..."
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'couchrest'
3
+
4
+ couch = CouchRest.new("http://127.0.0.1:5984")
5
+ db = couch.database('word-count-example')
6
+
7
+ word_count = {
8
+ :map => 'function(doc){
9
+ var words = doc.text.split(/\W/);
10
+ words.forEach(function(word){
11
+ if (word.length > 0) emit([word,doc.title],1);
12
+ });
13
+ }',
14
+ :reduce => 'function(key,combine){
15
+ return sum(combine);
16
+ }'
17
+ }
18
+
19
+ db.delete db.get("_design/word_count") rescue nil
20
+
21
+ db.save({
22
+ "_id" => "_design/word_count",
23
+ :views => {
24
+ :words => word_count
25
+ }
26
+ })
@@ -0,0 +1,71 @@
1
+ require 'fileutils'
2
+
3
+ module CouchRest
4
+ module Commands
5
+ module Generate
6
+
7
+ def self.run(options)
8
+ directory = options[:directory]
9
+ design_names = options[:trailing_args]
10
+
11
+ FileUtils.mkdir_p(directory)
12
+ filename = File.join(directory, "lib.js")
13
+ self.write(filename, <<-FUNC)
14
+ // Put global functions here.
15
+ // Include in your views with
16
+ //
17
+ // //include-lib
18
+ FUNC
19
+
20
+ design_names.each do |design_name|
21
+ subdirectory = File.join(directory, design_name)
22
+ FileUtils.mkdir_p(subdirectory)
23
+ filename = File.join(subdirectory, "sample-map.js")
24
+ self.write(filename, <<-FUNC)
25
+ function(doc) {
26
+ // Keys is first letter of _id
27
+ emit(doc._id[0], doc);
28
+ }
29
+ FUNC
30
+
31
+ filename = File.join(subdirectory, "sample-reduce.js")
32
+ self.write(filename, <<-FUNC)
33
+ function(keys, values) {
34
+ // Count the number of keys starting with this letter
35
+ return values.length;
36
+ }
37
+ FUNC
38
+
39
+ filename = File.join(subdirectory, "lib.js")
40
+ self.write(filename, <<-FUNC)
41
+ // Put functions specific to '#{design_name}' here.
42
+ // Include in your views with
43
+ //
44
+ // //include-lib
45
+ FUNC
46
+ end
47
+ end
48
+
49
+ def self.help
50
+ helpstring = <<-GEN
51
+
52
+ Usage: couchview generate directory design1 design2 design3 ...
53
+
54
+ Couchview will create directories and example views for the design documents you specify.
55
+
56
+ GEN
57
+ helpstring.gsub(/^ /, '')
58
+ end
59
+
60
+ def self.write(filename, contents)
61
+ puts "Writing #{filename}"
62
+ File.open(filename, "w") do |f|
63
+ # Remove leading spaces
64
+ contents.gsub!(/^ ( )?/, '')
65
+ f.write contents
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,103 @@
1
+ module CouchRest
2
+
3
+ module Commands
4
+
5
+ module Push
6
+
7
+ def self.run(options)
8
+ directory = options[:directory]
9
+ database = options[:trailing_args].first
10
+
11
+ fm = CouchRest::FileManager.new(database)
12
+ fm.loud = options[:loud]
13
+
14
+ if options[:loud]
15
+ puts "Pushing views from directory #{directory} to database #{fm.db}"
16
+ end
17
+
18
+ fm.push_views(directory)
19
+ end
20
+
21
+ def self.help
22
+ helpstring = <<-GEN
23
+
24
+ == Pushing views with Couchview ==
25
+
26
+ Usage: couchview push directory dbname
27
+
28
+ Couchview expects a specific filesystem layout for your CouchDB views (see
29
+ example below). It also supports advanced features like inlining of library
30
+ code (so you can keep DRY) as well as avoiding unnecessary document
31
+ modification.
32
+
33
+ Couchview also solves a problem with CouchDB's view API, which only provides
34
+ access to the final reduce side of any views which have both a map and a
35
+ reduce function defined. The intermediate map results are often useful for
36
+ development and production. CouchDB is smart enough to reuse map indexes for
37
+ functions duplicated across views within the same design document.
38
+
39
+ For views with a reduce function defined, Couchview creates both a reduce view
40
+ and a map-only view, so that you can browse and query the map side as well as
41
+ the reduction, with no performance penalty.
42
+
43
+ == Example ==
44
+
45
+ couchview push foo-project/bar-views baz-database
46
+
47
+ This will push the views defined in foo-project/bar-views into a database
48
+ called baz-database. Couchview expects the views to be defined in files with
49
+ names like:
50
+
51
+ foo-project/bar-views/my-design/viewname-map.js
52
+ foo-project/bar-views/my-design/viewname-reduce.js
53
+ foo-project/bar-views/my-design/noreduce-map.js
54
+
55
+ Pushed to => http://127.0.0.1:5984/baz-database/_design/my-design
56
+
57
+ And the design document:
58
+ {
59
+ "views" : {
60
+ "viewname-map" : {
61
+ "map" : "### contents of view-name-map.js ###"
62
+ },
63
+ "viewname-reduce" : {
64
+ "map" : "### contents of view-name-map.js ###",
65
+ "reduce" : "### contents of view-name-reduce.js ###"
66
+ },
67
+ "noreduce-map" : {
68
+ "map" : "### contents of noreduce-map.js ###"
69
+ }
70
+ }
71
+ }
72
+
73
+ Couchview will create a design document for each subdirectory of the views
74
+ directory specified on the command line.
75
+
76
+ == Library Inlining ==
77
+
78
+ Couchview can optionally inline library code into your views so you only have
79
+ to maintain it in one place. It looks for any files named lib.* in your
80
+ design-doc directory (for doc specific libs) and in the parent views directory
81
+ (for project global libs). These libraries are only inserted into views which
82
+ include the text
83
+
84
+ // !include lib
85
+
86
+ or
87
+
88
+ # !include lib
89
+
90
+ Couchview is a result of scratching my own itch. I'd be happy to make it more
91
+ general, so please contact me at jchris@grabb.it if you'd like to see anything
92
+ added or changed.
93
+
94
+ GEN
95
+ helpstring.gsub(/^ /, '')
96
+ end
97
+
98
+ end
99
+
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,314 @@
1
+ require 'cgi'
2
+ require "base64"
3
+
4
+ module CouchRest
5
+ class Database
6
+ attr_reader :server, :host, :name, :root, :uri
7
+ attr_accessor :bulk_save_cache_limit
8
+
9
+ # Create a CouchRest::Database adapter for the supplied CouchRest::Server
10
+ # and database name.
11
+ #
12
+ # ==== Parameters
13
+ # server<CouchRest::Server>:: database host
14
+ # name<String>:: database name
15
+ #
16
+ def initialize(server, name)
17
+ @name = name
18
+ @server = server
19
+ @host = server.uri
20
+ @uri = @root = "#{host}/#{name}"
21
+ @streamer = Streamer.new(self)
22
+ @bulk_save_cache = []
23
+ @bulk_save_cache_limit = 50
24
+ end
25
+
26
+ # returns the database's uri
27
+ def to_s
28
+ @uri
29
+ end
30
+
31
+ # GET the database info from CouchDB
32
+ def info
33
+ CouchRest.get @uri
34
+ end
35
+
36
+ # Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
37
+ def documents(params = {})
38
+ keys = params.delete(:keys)
39
+ url = CouchRest.paramify_url "#{@uri}/_all_docs", params
40
+ if keys
41
+ CouchRest.post(url, {:keys => keys})
42
+ else
43
+ CouchRest.get url
44
+ end
45
+ end
46
+
47
+ # POST a temporary view function to CouchDB for querying. This is not
48
+ # recommended, as you don't get any performance benefit from CouchDB's
49
+ # materialized views. Can be quite slow on large databases.
50
+ def slow_view(funcs, params = {})
51
+ keys = params.delete(:keys)
52
+ funcs = funcs.merge({:keys => keys}) if keys
53
+ url = CouchRest.paramify_url "#{@uri}/_temp_view", params
54
+ JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
55
+ end
56
+
57
+ # backwards compatibility is a plus
58
+ alias :temp_view :slow_view
59
+
60
+ # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
61
+ # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
62
+ def view(name, params = {}, &block)
63
+ keys = params.delete(:keys)
64
+ url = CouchRest.paramify_url "#{@uri}/_view/#{name}", params
65
+ if keys
66
+ CouchRest.post(url, {:keys => keys})
67
+ else
68
+ if block_given?
69
+ @streamer.view(name, params, &block)
70
+ else
71
+ CouchRest.get url
72
+ end
73
+ end
74
+ end
75
+
76
+ # GET a document from CouchDB, by id. Returns a Ruby Hash.
77
+ def get(id)
78
+ slug = escape_docid(id)
79
+ hash = CouchRest.get("#{@uri}/#{slug}")
80
+ doc = if /^_design/ =~ hash["_id"]
81
+ Design.new(hash)
82
+ else
83
+ Document.new(hash)
84
+ end
85
+ doc.database = self
86
+ doc
87
+ end
88
+
89
+ # GET an attachment directly from CouchDB
90
+ def fetch_attachment(doc, name)
91
+ # slug = escape_docid(docid)
92
+ # name = CGI.escape(name)
93
+
94
+ uri = uri_for_attachment(doc, name)
95
+
96
+ RestClient.get uri
97
+ # "#{@uri}/#{slug}/#{name}"
98
+ end
99
+
100
+ # PUT an attachment directly to CouchDB
101
+ def put_attachment(doc, name, file, options = {})
102
+ docid = escape_docid(doc['_id'])
103
+ name = CGI.escape(name)
104
+ uri = uri_for_attachment(doc, name)
105
+ JSON.parse(RestClient.put(uri, file, options))
106
+ end
107
+
108
+ # DELETE an attachment directly from CouchDB
109
+ def delete_attachment doc, name
110
+ uri = uri_for_attachment(doc, name)
111
+ # this needs a rev
112
+ JSON.parse(RestClient.delete(uri))
113
+ end
114
+
115
+ # Save a document to CouchDB. This will use the <tt>_id</tt> field from
116
+ # the document as the id for PUT, or request a new UUID from CouchDB, if
117
+ # no <tt>_id</tt> is present on the document. IDs are attached to
118
+ # documents on the client side because POST has the curious property of
119
+ # being automatically retried by proxies in the event of network
120
+ # segmentation and lost responses.
121
+ #
122
+ # If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
123
+ # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
124
+ def save_doc(doc, bulk = false)
125
+ if doc['_attachments']
126
+ doc['_attachments'] = encode_attachments(doc['_attachments'])
127
+ end
128
+ if bulk
129
+ @bulk_save_cache << doc
130
+ return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
131
+ return {"ok" => true} # Compatibility with Document#save
132
+ elsif !bulk && @bulk_save_cache.length > 0
133
+ bulk_save
134
+ end
135
+ result = if doc['_id']
136
+ slug = escape_docid(doc['_id'])
137
+ CouchRest.put "#{@uri}/#{slug}", doc
138
+ else
139
+ begin
140
+ slug = doc['_id'] = @server.next_uuid
141
+ CouchRest.put "#{@uri}/#{slug}", doc
142
+ rescue #old version of couchdb
143
+ CouchRest.post @uri, doc
144
+ end
145
+ end
146
+ if result['ok']
147
+ doc['_id'] = result['id']
148
+ doc['_rev'] = result['rev']
149
+ doc.database = self if doc.respond_to?(:database=)
150
+ end
151
+ result
152
+ end
153
+
154
+ ### DEPRECATION NOTICE
155
+ def save(doc, bulk=false)
156
+ puts "CouchRest::Database's save method is being deprecated, please use save_doc instead"
157
+ save_doc(doc, bulk)
158
+ end
159
+
160
+
161
+ # POST an array of documents to CouchDB. If any of the documents are
162
+ # missing ids, supply one from the uuid cache.
163
+ #
164
+ # If called with no arguments, bulk saves the cache of documents to be bulk saved.
165
+ def bulk_save(docs = nil, use_uuids = true)
166
+ if docs.nil?
167
+ docs = @bulk_save_cache
168
+ @bulk_save_cache = []
169
+ end
170
+ if (use_uuids)
171
+ ids, noids = docs.partition{|d|d['_id']}
172
+ uuid_count = [noids.length, @server.uuid_batch_count].max
173
+ noids.each do |doc|
174
+ nextid = @server.next_uuid(uuid_count) rescue nil
175
+ doc['_id'] = nextid if nextid
176
+ end
177
+ end
178
+ CouchRest.post "#{@uri}/_bulk_docs", {:docs => docs}
179
+ end
180
+
181
+ # DELETE the document from CouchDB that has the given <tt>_id</tt> and
182
+ # <tt>_rev</tt>.
183
+ #
184
+ # If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
185
+ # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
186
+ def delete_doc(doc, bulk = false)
187
+ raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
188
+ if bulk
189
+ @bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
190
+ return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
191
+ return { "ok" => true } # Mimic the non-deferred version
192
+ end
193
+ slug = escape_docid(doc['_id'])
194
+ CouchRest.delete "#{@uri}/#{slug}?rev=#{doc['_rev']}"
195
+ end
196
+
197
+ ### DEPRECATION NOTICE
198
+ def delete(doc, bulk=false)
199
+ puts "CouchRest::Database's delete method is being deprecated, please use delete_doc instead"
200
+ delete_doc(doc, bulk)
201
+ end
202
+
203
+ # COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
204
+ # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
205
+ # hash with a '_rev' key
206
+ def copy_doc(doc, dest)
207
+ raise ArgumentError, "_id is required for copying" unless doc['_id']
208
+ slug = escape_docid(doc['_id'])
209
+ destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
210
+ "#{dest['_id']}?rev=#{dest['_rev']}"
211
+ else
212
+ dest
213
+ end
214
+ CouchRest.copy "#{@uri}/#{slug}", destination
215
+ end
216
+
217
+ ### DEPRECATION NOTICE
218
+ def copy(doc, dest)
219
+ puts "CouchRest::Database's copy method is being deprecated, please use copy_doc instead"
220
+ copy_doc(doc, dest)
221
+ end
222
+
223
+ # MOVE an existing document to a new id. If the destination id currently exists, a rev must be provided.
224
+ # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
225
+ # hash with a '_rev' key
226
+ def move_doc(doc, dest)
227
+ raise ArgumentError, "_id and _rev are required for moving" unless doc['_id'] && doc['_rev']
228
+ slug = escape_docid(doc['_id'])
229
+ destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
230
+ "#{dest['_id']}?rev=#{dest['_rev']}"
231
+ else
232
+ dest
233
+ end
234
+ CouchRest.move "#{@uri}/#{slug}?rev=#{doc['_rev']}", destination
235
+ end
236
+
237
+ ### DEPRECATION NOTICE
238
+ def move(doc, dest)
239
+ puts "CouchRest::Database's move method is being deprecated, please use move_doc instead"
240
+ move_doc(doc, dest)
241
+ end
242
+
243
+ # Compact the database, removing old document revisions and optimizing space use.
244
+ def compact!
245
+ CouchRest.post "#{@uri}/_compact"
246
+ end
247
+
248
+ # Create the database
249
+ def create!
250
+ bool = server.create_db(@name) rescue false
251
+ bool && true
252
+ end
253
+
254
+ # Delete and re create the database
255
+ def recreate!
256
+ delete!
257
+ create!
258
+ rescue RestClient::ResourceNotFound
259
+ ensure
260
+ create!
261
+ end
262
+
263
+ # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
264
+ def replicate_from other_db
265
+ raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
266
+ CouchRest.post "#{@host}/_replicate", :source => other_db.root, :target => name
267
+ end
268
+
269
+ # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
270
+ def replicate_to other_db
271
+ raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
272
+ CouchRest.post "#{@host}/_replicate", :target => other_db.root, :source => name
273
+ end
274
+
275
+ # DELETE the database itself. This is not undoable and could be rather
276
+ # catastrophic. Use with care!
277
+ def delete!
278
+ CouchRest.delete @uri
279
+ end
280
+
281
+ private
282
+
283
+ def uri_for_attachment doc, name
284
+ if doc.is_a?(String)
285
+ puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
286
+ docid = doc
287
+ rev = nil
288
+ else
289
+ docid = doc['_id']
290
+ rev = doc['_rev']
291
+ end
292
+ docid = escape_docid(docid)
293
+ name = CGI.escape(name)
294
+ rev = "?rev=#{doc['_rev']}" if rev
295
+ "#{@root}/#{docid}/#{name}#{rev}"
296
+ end
297
+
298
+ def escape_docid id
299
+ /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
300
+ end
301
+
302
+ def encode_attachments(attachments)
303
+ attachments.each do |k,v|
304
+ next if v['stub']
305
+ v['data'] = base64(v['data'])
306
+ end
307
+ attachments
308
+ end
309
+
310
+ def base64(data)
311
+ Base64.encode64(data).gsub(/\s/,'')
312
+ end
313
+ end
314
+ end