Rebased
* '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:
commit
fa29906900
|
@ -87,25 +87,31 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
# GET an attachment directly from CouchDB
|
# GET an attachment directly from CouchDB
|
||||||
def fetch_attachment(docid, name)
|
def fetch_attachment(doc, name)
|
||||||
slug = escape_docid(docid)
|
# slug = escape_docid(docid)
|
||||||
name = CGI.escape(name)
|
# name = CGI.escape(name)
|
||||||
RestClient.get "#{@uri}/#{slug}/#{name}"
|
|
||||||
|
uri = uri_for_attachment(doc, name)
|
||||||
|
|
||||||
|
RestClient.get uri
|
||||||
|
# "#{@uri}/#{slug}/#{name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# PUT an attachment directly to CouchDB
|
# PUT an attachment directly to CouchDB
|
||||||
def put_attachment(doc, name, file, options = {})
|
def put_attachment(doc, name, file, options = {})
|
||||||
docid = escape_docid(doc['_id'])
|
docid = escape_docid(doc['_id'])
|
||||||
name = CGI.escape(name)
|
name = CGI.escape(name)
|
||||||
uri = if doc['_rev']
|
uri = uri_for_attachment(doc, name)
|
||||||
"#{@uri}/#{docid}/#{name}?rev=#{doc['_rev']}"
|
|
||||||
else
|
|
||||||
"#{@uri}/#{docid}/#{name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
JSON.parse(RestClient.put(uri, file, options))
|
JSON.parse(RestClient.put(uri, file, options))
|
||||||
end
|
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
|
# 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
|
# 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
|
# no <tt>_id</tt> is present on the document. IDs are attached to
|
||||||
|
@ -254,6 +260,18 @@ module CouchRest
|
||||||
create!
|
create!
|
||||||
end
|
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
|
# DELETE the database itself. This is not undoable and could be rather
|
||||||
# catastrophic. Use with care!
|
# catastrophic. Use with care!
|
||||||
def delete!
|
def delete!
|
||||||
|
@ -262,7 +280,22 @@ module CouchRest
|
||||||
|
|
||||||
private
|
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)
|
/^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,32 @@ module CouchRest
|
||||||
@@database
|
@@database
|
||||||
end
|
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
|
# Returns the CouchDB uri for the document
|
||||||
def uri(append_rev = false)
|
def uri(append_rev = false)
|
||||||
return nil if new_document?
|
return nil if new_document?
|
||||||
|
@ -47,6 +73,29 @@ module CouchRest
|
||||||
@database || self.class.database
|
@database || self.class.database
|
||||||
end
|
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
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -231,6 +231,30 @@ 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
|
describe "PUT attachment from file" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
filename = FIXTURE_PATH + '/attachments/couchdb.png'
|
filename = FIXTURE_PATH + '/attachments/couchdb.png'
|
||||||
|
@ -331,6 +355,27 @@ describe CouchRest::Database do
|
||||||
end
|
end
|
||||||
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
|
describe "POST document with attachment (with funky name)" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@attach = "<html><head><title>My Funky Doc</title></head><body><p>Has words.</p></body></html>"
|
@attach = "<html><head><title>My Funky Doc</title></head><body><p>Has words.</p></body></html>"
|
||||||
|
@ -625,6 +670,37 @@ describe CouchRest::Database do
|
||||||
end
|
end
|
||||||
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
|
describe "creating a database" do
|
||||||
before(:each) do
|
before(:each) do
|
||||||
@db = @cr.database('couchrest-test-db_to_create')
|
@db = @cr.database('couchrest-test-db_to_create')
|
||||||
|
|
|
@ -142,6 +142,10 @@ describe CouchRest::Document do
|
||||||
end
|
end
|
||||||
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
|
describe "destroying a document from a db using bulk save" do
|
||||||
before(:all) do
|
before(:all) do
|
||||||
|
@ -243,4 +247,68 @@ describe CouchRest::Document do
|
||||||
end
|
end
|
||||||
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
|
end
|
Loading…
Reference in a new issue