require 'cgi' require "base64" module CouchRest class Database attr_reader :server, :host, :name, :root # Create a CouchRest::Database adapter for the supplied CouchRest::Server and database name. # # ==== Parameters # server:: database host # name:: database name # def initialize server, name @name = name @server = server @host = server.uri @root = "#{host}/#{name}" end # returns the database's uri def to_s @root end # GET the database info from CouchDB def info CouchRest.get @root end # Query the _all_docs view. Accepts all the same arguments as view. def documents params = nil url = CouchRest.paramify_url "#{@root}/_all_docs", params CouchRest.get url end # POST a temporary view function to CouchDB for querying. This is not recommended, as you don't get any performance benefit from CouchDB's materialized views. Can be quite slow on large databases. def temp_view funcs, params = nil url = CouchRest.paramify_url "#{@root}/_temp_view", params JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'})) end # Query a CouchDB view as defined by a _design document. Accepts paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi def view name, params = nil url = CouchRest.paramify_url "#{@root}/_view/#{name}", params CouchRest.get url end # GET a document from CouchDB, by id. Returns a Ruby Hash. def get id slug = CGI.escape(id) CouchRest.get "#{@root}/#{slug}" end # GET an attachment directly from CouchDB def fetch_attachment doc, name doc = CGI.escape(doc) name = CGI.escape(name) RestClient.get "#{@root}/#{doc}/#{name}" end # PUT an attachment directly to CouchDB def put_attachment doc, name, file, options = {} docid = CGI.escape(doc['_id']) name = CGI.escape(name) uri = if doc['_rev'] "#{@root}/#{docid}/#{name}?rev=#{doc['_rev']}" else "#{@root}/#{docid}/#{name}" end JSON.parse(RestClient.put(uri, file, options)) 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 documents on the client side because POST has the curious property of being automatically retried by proxies in the event of network segmentation and lost responses. def save doc if doc['_attachments'] doc['_attachments'] = encode_attachments(doc['_attachments']) end if doc['_id'] slug = CGI.escape(doc['_id']) CouchRest.put "#{@root}/#{slug}", doc else begin slug = doc['_id'] = @server.next_uuid CouchRest.put "#{@root}/#{slug}", doc rescue #old version of couchdb CouchRest.post @root, doc end end end # POST an array of documents to CouchDB. If any of the documents are missing ids, supply one from the uuid cache. def bulk_save docs ids, noids = docs.partition{|d|d['_id']} uuid_count = [noids.length, @server.uuid_batch_count].max noids.each do |doc| nextid = @server.next_uuid(uuid_count) rescue nil doc['_id'] = nextid if nextid end CouchRest.post "#{@root}/_bulk_docs", {:docs => docs} end # DELETE the document from CouchDB that has the given _id and _rev. def delete doc slug = CGI.escape(doc['_id']) CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}" end # DELETE the database itself. This is not undoable and could be rather catastrophic. Use with care! def delete! CouchRest.delete @root end private def encode_attachments attachments attachments.each do |k,v| next if v['stub'] v['data'] = base64(v['data']) end attachments end def base64 data Base64.encode64(data).gsub(/\s/,'') end end end