* 'master' of git://github.com/jchris/couchrest:
  all specs pass; refined attachment api
  add mattetti's 5aebd53a93
  fix rebase end balance
  Started on the ExtendedDocument class with features moved to mixins.
  Started on the ExtendedDocument class with features moved to mixins.
  updated readme file
  Started the refactoring work on couchrest.
  added some monkey patches to improve the http connection speed. (by keeping the http connection open)
  slight change of API, CR::Document now uses <action>_doc instead of <action>, also added #create! and #recreate! to Document instances
  Added attachment methods to CouchRest::Document: #put_attachment, #fetch_attachment and #delete_attachment. Note you can overwrite exisitng attachments with #put_attachment.
  - Added Database#delete_attachment, for removing them directly
  documentation for Document#copy and #move, copied from Database
  database replication methods, no conflict resolution provided
This commit is contained in:
Matt Aimonetti 2009-02-02 15:40:49 -08:00
commit fa29906900
6 changed files with 244 additions and 18 deletions

View file

@ -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 <tt>_id</tt> field from
# the document as the id for PUT, or request a new UUID from CouchDB, if
# no <tt>_id</tt> 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

View file

@ -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.
# <tt>dest</tt> 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.
# <tt>dest</tt> 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

View file

@ -45,4 +45,4 @@ module CouchRest
end
end
end
end

View file

@ -111,4 +111,4 @@ module CouchRest
end
end
end
end

View file

@ -230,7 +230,31 @@ describe CouchRest::Database do
end
end
describe "fetch_attachment" do
before do
@attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
@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' => "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
}
}
}
@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')

View file

@ -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 = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
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 = "<html><head><title>My Doc</title></head><body><p>Is Different.</p></body></html>"
@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