diff --git a/lib/couchrest/core/database.rb b/lib/couchrest/core/database.rb index 1d2433c..36597fc 100644 --- a/lib/couchrest/core/database.rb +++ b/lib/couchrest/core/database.rb @@ -249,6 +249,33 @@ module CouchRest CouchRest.copy "#{@root}/#{slug}", destination end + # Updates the given doc by yielding the current state of the doc + # and trying to update update_limit times. Returns the new doc + # if the doc was successfully updated without hitting the limit + def update_doc(doc_id, params = {}, update_limit=10) + resp = {'ok' => false} + new_doc = nil + last_fail = nil + + until resp['ok'] or update_limit <= 0 + doc = self.get(doc_id, params) # grab the doc + new_doc = yield doc # give it to the caller to be updated + begin + resp = self.save_doc new_doc # try to PUT the updated doc into the db + rescue RestClient::RequestFailed => e + if e.http_code == 409 # Update collision + update_limit -= 1 + last_fail = e + else # some other error + raise e + end + end + end + + raise last_fail unless resp['ok'] + new_doc + end + # Compact the database, removing old document revisions and optimizing space use. def compact! CouchRest.post "#{@root}/_compact" diff --git a/spec/couchrest/core/database_spec.rb b/spec/couchrest/core/database_spec.rb index 121548c..28a57dd 100644 --- a/spec/couchrest/core/database_spec.rb +++ b/spec/couchrest/core/database_spec.rb @@ -551,6 +551,53 @@ describe CouchRest::Database do end + describe "UPDATE existing document" do + before :each do + @id = @db.save_doc({ + 'article' => 'Pete Doherty Kicked Out For Nazi Anthem', + 'upvotes' => 10, + 'link' => 'http://beatcrave.com/2009-11-30/pete-doherty-kicked-out-for-nazi-anthem/'})['id'] + end + it "should work under normal conditions" do + @db.update_doc @id do |doc| + doc['upvotes'] += 1 + doc + end + @db.get(@id)['upvotes'].should == 11 + end + it "should fail if update_limit is reached" do + lambda do + @db.update_doc @id do |doc| + # modify and save the doc so that a collision happens + conflicting_doc = @db.get @id + conflicting_doc['upvotes'] += 1 + @db.save_doc conflicting_doc + + # then try saving it through the update + doc['upvotes'] += 1 + doc + end + end.should raise_error(RestClient::RequestFailed) + end + it "should not fail if update_limit is not reached" do + limit = 5 + lambda do + @db.update_doc @id do |doc| + # same as the last spec except we're only forcing 5 conflicts + if limit > 0 + conflicting_doc = @db.get @id + conflicting_doc['upvotes'] += 1 + @db.save_doc conflicting_doc + limit -= 1 + end + doc['upvotes'] += 1 + doc + end + end.should_not raise_error + @db.get(@id)['upvotes'].should == 16 + end + end + describe "COPY existing document" do before :each do @r = @db.save_doc({'artist' => 'Zappa', 'title' => 'Muffin Man'})