diff --git a/lib/couchrest/core/database.rb b/lib/couchrest/core/database.rb index eee03b5..ed98c16 100644 --- a/lib/couchrest/core/database.rb +++ b/lib/couchrest/core/database.rb @@ -87,25 +87,31 @@ module CouchRest end # GET an attachment directly from CouchDB - def fetch_attachment(docid, name) - slug = escape_docid(docid) - name = CGI.escape(name) - RestClient.get "#{@uri}/#{slug}/#{name}" + def fetch_attachment(doc, name) + # slug = escape_docid(docid) + # name = CGI.escape(name) + + uri = uri_for_attachment(doc, name) + + RestClient.get uri + # "#{@uri}/#{slug}/#{name}" end # PUT an attachment directly to CouchDB def put_attachment(doc, name, file, options = {}) docid = escape_docid(doc['_id']) name = CGI.escape(name) - uri = if doc['_rev'] - "#{@uri}/#{docid}/#{name}?rev=#{doc['_rev']}" - else - "#{@uri}/#{docid}/#{name}" - end - + uri = uri_for_attachment(doc, name) JSON.parse(RestClient.put(uri, file, options)) end + # DELETE an attachment directly from CouchDB + def delete_attachment doc, name + uri = uri_for_attachment(doc, name) + # this needs a rev + JSON.parse(RestClient.delete(uri)) + end + # Save a document to CouchDB. This will use the _id field from # the document as the id for PUT, or request a new UUID from CouchDB, if # no _id is present on the document. IDs are attached to @@ -253,7 +259,19 @@ module CouchRest ensure create! end - + + # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts. + def replicate_from other_db + raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database) + CouchRest.post "#{@host}/_replicate", :source => other_db.root, :target => name + end + + # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts. + def replicate_to other_db + raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database) + CouchRest.post "#{@host}/_replicate", :target => other_db.root, :source => name + end + # DELETE the database itself. This is not undoable and could be rather # catastrophic. Use with care! def delete! @@ -261,8 +279,23 @@ module CouchRest end private - - def escape_docid(id) + + def uri_for_attachment doc, name + if doc.is_a?(String) + puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id" + docid = doc + rev = nil + else + docid = doc['_id'] + rev = doc['_rev'] + end + docid = escape_docid(docid) + name = CGI.escape(name) + rev = "?rev=#{doc['_rev']}" if rev + "#{@root}/#{docid}/#{name}#{rev}" + end + + def escape_docid id /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id) end diff --git a/lib/couchrest/core/document.rb b/lib/couchrest/core/document.rb index 38d965c..59e09f1 100644 --- a/lib/couchrest/core/document.rb +++ b/lib/couchrest/core/document.rb @@ -30,6 +30,32 @@ module CouchRest @@database end + def id + self['_id'] + end + + def rev + self['_rev'] + end + + # copies the document to a new id. If the destination id currently exists, a rev must be provided. + # dest can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc + # hash with a '_rev' key + def copy(dest) + raise ArgumentError, "doc.database required to copy" unless database + result = database.copy_doc(self, dest) + result['ok'] + end + + # moves the document to a new id. If the destination id currently exists, a rev must be provided. + # dest can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc + # hash with a '_rev' key + def move(dest) + raise ArgumentError, "doc.database required to copy" unless database + result = database.move_doc(self, dest) + result['ok'] + end + # Returns the CouchDB uri for the document def uri(append_rev = false) return nil if new_document? @@ -46,7 +72,30 @@ module CouchRest def database @database || self.class.database end - + + # saves an attachment directly to couchdb + def put_attachment(name, file, options={}) + raise ArgumentError, "doc must be saved" unless self.rev + raise ArgumentError, "doc.database required to put_attachment" unless database + result = database.put_attachment(self, name, file, options) + self['_rev'] = result['rev'] + result['ok'] + end + + # returns an attachment's data + def fetch_attachment(name) + raise ArgumentError, "doc must be saved" unless self.rev + raise ArgumentError, "doc.database required to put_attachment" unless database + database.fetch_attachment(self, name) + end + + # deletes an attachment directly from couchdb + def delete_attachment(name) + raise ArgumentError, "doc.database required to delete_attachment" unless database + result = database.delete_attachment(self, name) + self['_rev'] = result['rev'] + result['ok'] + end end end diff --git a/lib/couchrest/mixins/document_queries.rb b/lib/couchrest/mixins/document_queries.rb index 086172c..bd7e450 100644 --- a/lib/couchrest/mixins/document_queries.rb +++ b/lib/couchrest/mixins/document_queries.rb @@ -45,4 +45,4 @@ module CouchRest end end -end +end \ No newline at end of file diff --git a/lib/couchrest/more/extended_document.rb b/lib/couchrest/more/extended_document.rb index e909d3d..40bbfd6 100644 --- a/lib/couchrest/more/extended_document.rb +++ b/lib/couchrest/more/extended_document.rb @@ -111,4 +111,4 @@ module CouchRest end end -end +end \ No newline at end of file diff --git a/spec/couchrest/core/database_spec.rb b/spec/couchrest/core/database_spec.rb index e32b176..7e621c2 100644 --- a/spec/couchrest/core/database_spec.rb +++ b/spec/couchrest/core/database_spec.rb @@ -230,7 +230,31 @@ describe CouchRest::Database do end end - + + describe "fetch_attachment" do + before do + @attach = "My Doc

Has words.

" + @doc = { + "_id" => "mydocwithattachment", + "field" => ["some value"], + "_attachments" => { + "test.html" => { + "type" => "text/html", + "data" => @attach + } + } + } + @db.save(@doc) + end + + it "should get the attachment with the doc's _id" do + @db.fetch_attachment("mydocwithattachment", "test.html").should == @attach + end + it "should get the attachment with the doc itself" do + @db.fetch_attachment(@db.get('mydocwithattachment'), 'test.html').should == @attach + end + end + describe "PUT attachment from file" do before(:each) do filename = FIXTURE_PATH + '/attachments/couchdb.png' @@ -330,6 +354,27 @@ describe CouchRest::Database do attachment.should == @attach2 end end + + describe "DELETE an attachment directly from the database" do + before(:each) do + doc = { + '_id' => 'mydocwithattachment', + '_attachments' => { + 'test.html' => { + 'type' => 'text/html', + 'data' => "My Doc

Has words.

" + } + } + } + @db.save(doc) + @doc = @db.get('mydocwithattachment') + end + it "should delete the attachment" do + lambda { @db.fetch_attachment('mydocwithattachment','test.html') }.should_not raise_error + @db.delete_attachment(@doc, "test.html") + lambda { @db.fetch_attachment('mydocwithattachment','test.html') }.should raise_error(RestClient::ResourceNotFound) + end + end describe "POST document with attachment (with funky name)" do before(:each) do @@ -625,6 +670,37 @@ describe CouchRest::Database do end end + describe "replicating a database" do + before do + @db.save({'_id' => 'test_doc', 'some-value' => 'foo'}) + @other_db = @cr.database 'couchrest-test-replication' + @other_db.delete! rescue nil + @other_db = @cr.create_db 'couchrest-test-replication' + end + + describe "via pulling" do + before do + @other_db.replicate_from @db + end + + it "contains the document from the original database" do + doc = @other_db.get('test_doc') + doc['some-value'].should == 'foo' + end + end + + describe "via pushing" do + before do + @db.replicate_to @other_db + end + + it "copies the document to the other database" do + doc = @other_db.get('test_doc') + doc['some-value'].should == 'foo' + end + end + end + describe "creating a database" do before(:each) do @db = @cr.database('couchrest-test-db_to_create') diff --git a/spec/couchrest/core/document_spec.rb b/spec/couchrest/core/document_spec.rb index 1f3b115..c165f4e 100644 --- a/spec/couchrest/core/document_spec.rb +++ b/spec/couchrest/core/document_spec.rb @@ -142,6 +142,10 @@ describe CouchRest::Document do end end + describe "bulk saving" do + before :all do + @db = reset_test_db! + end describe "destroying a document from a db using bulk save" do before(:all) do @@ -243,4 +247,68 @@ describe CouchRest::Document do end end end -end + +describe "dealing with attachments" do + before do + @db = reset_test_db! + @attach = "My Doc

Has words.

" + response = @db.save({'key' => 'value'}) + @doc = @db.get(response['id']) + end + + def append_attachment(name='test.html', attach=@attach) + @doc['_attachments'] ||= {} + @doc['_attachments'][name] = { + 'type' => 'text/html', + 'data' => attach + } + @doc.save + @rev = @doc['_rev'] + end + + describe "PUTing an attachment directly to the doc" do + before do + @doc.put_attachment('test.html', @attach) + end + + it "is there" do + @db.fetch_attachment(@doc, 'test.html').should == @attach + end + + it "updates the revision" do + @doc['_rev'].should_not == @rev + end + + it "updates attachments" do + @attach2 = "My Doc

Is Different.

" + @doc.put_attachment('test.html', @attach2) + @db.fetch_attachment(@doc, 'test.html').should == @attach2 + end + end + + describe "fetching an attachment from a doc directly" do + before do + append_attachment + end + + it "pulls the attachment" do + @doc.fetch_attachment('test.html').should == @attach + end + end + + describe "deleting an attachment from a doc directly" do + before do + append_attachment + @doc.delete_attachment('test.html') + end + + it "removes it" do + lambda { @db.fetch_attachment(@doc, 'test.html').should }.should raise_error(RestClient::ResourceNotFound) + end + + it "updates the revision" do + @doc['_rev'].should_not == @rev + end + end + +end \ No newline at end of file