Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Undeletes a document from a CouchDB database.
#!/usr/bin/env ruby
# Undelete a document from a CouchDB database.
# Recovers a previously deleted document by looking at the _changes
# feed, putting a new empty document preserving the revisions chain,
# retrieves the revs_info for the document, asks the user which one
# to recover, and puts back the old revision into place.
# For this to work, GETting the document must return error: "not_found",
# deleted: true.
# Requires Typhoeus and Oj.
# Latest version:
# h/t to
# - vjt Mon Aug 5 21:22:13 CEST 2013
# Made in Ventotene.
require 'typhoeus'
require 'oj'
def get(url, params = {})
res = Typhoeus.get(url, :params => params, :headers => {'Accept' => 'application/json'})
def put(url, body, params = {})
res = Typhoeus.put(url, :body => Oj.dump(body), :params => params, :headers => {'Accept' => 'application/json'})
def delete(url, params = {})
res = Typhoeus.delete(url, :params => params, :headers => {'Accept' => 'application/json'})
class UndeleteError < StandardError; end
if $0 == __FILE__
database = ARGV[0]
docid = ARGV[1]
unless database && docid
raise ArgumentError, "Usage: #$0 <Database> <Document ID>"
docurl = [database, docid].join('/')
changes = [database, '_changes'].join('/')
puts "Processing document #{docurl}..."
# Verify the document is deleted
res = get(docurl)
unless res == {"error"=>"not_found", "reason"=>"deleted"}
raise UndeleteError, "The specified document is not recoverable. Sorry, you'll have to recover from backup."
# Now get the last revision from _changes
res = get(changes)['results'].select {|d| d['id'] == docid && d['deleted'] == true}.last
unless res
raise UndeleteError, "Could not find the last delete change."
lastrev = res['changes'].first['rev']
# Put a new empty_version of the document
res = put(docurl, {}, {:rev => lastrev})
unless res['ok'] == true
raise UndeleteError, "Sorry, an unexpected error has occurred: #{res.inspect}"
currrev = res['rev']
# Show previous revisions
res = get(docurl, :revs_info => true)
puts "The following revisions have been found:"
puts res['_revs_info'].select {|r| r['status'] == 'available'}.map(&:inspect).tap(&:shift).join("\n")
print "Type the revision number you want to recover: "
rev = $stdin.gets.chomp
puts "OK, fetching revision #{rev}..."
recovered = get(docurl, :rev => rev, :attachments => true)
recovered['_rev'] = currrev
puts "Putting back the document into couchdb..."
res = put docurl, recovered, :rev => currrev
if res['ok'] == true
puts "All done. Check that #{docurl} now contains what you do expect."
puts "An error occurred: #{res}"
end # if $0 == __FILE__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.