Class: Cushion::Database

Inherits:
Object
  • Object
show all
Defined in:
lib/cushion/database.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, options = {}) ⇒ Database

Initializes a Cushion::Database instance.



10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/cushion/database.rb', line 10

def initialize(name, options = {})
  unless options[:server]
    raise ArgumentError, "server option must be provided"
  end
  unless name
    raise ArgumentError, "name must be provided"
  end
  # TODO: CouchDB has strict db naming requirements. Should validate.
  @name = CGI.escape(name.to_s)
  @server = options[:server]
  @use_tempfiles = options[:use_tempfiles]
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/cushion/database.rb', line 6

def name
  @name
end

#serverObject (readonly)

Returns the value of attribute server.



6
7
8
# File 'lib/cushion/database.rb', line 6

def server
  @server
end

#use_tempfilesObject

Returns the value of attribute use_tempfiles.



7
8
9
# File 'lib/cushion/database.rb', line 7

def use_tempfiles
  @use_tempfiles
end

Instance Method Details

#all(options = {}) ⇒ Object

Convenience method for querying the all_docs view. See #view for more information.



324
325
326
# File 'lib/cushion/database.rb', line 324

def all(options = {})
  view(:all, options)
end

#attach(id, filename, data, options = {}) ⇒ Object

Save a file attachment under an existing document, or create a new document containing an attachment. id is the document id. filename is the name of the attachment (extensions may be omitted). Set the rev option to store the attachment under an existing document. Example:

db.attach("docid", "foo.txt", somedata, :rev => "1234")
#=> Stores an attachment under docid

db.attach("anotherid", "bar.jpg", :content_type => "image/jpeg")
#=> Creates a new document containing the attachment. Also sets the
    content type.

+data+ may be a <tt>String</tt>, or any object that responds to read.

Content type defaults to ‘application/octet-stream’.



140
141
142
143
144
145
146
147
148
# File 'lib/cushion/database.rb', line 140

def attach(id, filename, data, options = {})
  headers = options.delete(:headers) || {}
  headers[:content_type] = options.delete(:content_type) || "application/octet-stream"
  rev = options.delete(:rev)
  slug = Cushion.escape_key(id, filename)
  rev_slug = "?rev=#{rev}" if rev
  data = data.respond_to?(:read) ? data.read : data
  put("#{slug}#{rev_slug}", data, headers)
end

#bulk(docs, options = {}) ⇒ Object

Creates, updates and deletes multiple documents in this database according to the supplied docs array. Set the _deleted attribute to true to delete an individual doc. Set the _rev attribute to update an individual doc. Leave out the _rev attribute to create a new doc. Example:

docs = [
  { "_id" => "0", "_rev" => "123456", "_deleted" => true }, #=> Delete this doc
  { "_id" => "1", "_rev" => "32486671", "foo" => "bar" },   #=> Update this doc
  { "_id" => "2", "baz" => "bat" }                          #=> Create this doc
]

db.bulk(docs)

bulk returns a hash of updated doc ids and revs, as follows:

{
  "ok" => true,
  "new_revs" => [
    { "id" => "0", "rev" => "3682408536" },
    { "id" => "1", "rev" => "3206753266" },
    { "id" => "2", "rev" => "426742535" }
  ]
}

Set the use_uuids option to generate UUIDs from the server cache before saving. Set the headers option to pass custom request headers.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/cushion/database.rb', line 187

def bulk(docs, options = {})
  delete_all = options.delete(:delete)
  headers = options.delete(:headers) || {}
  use_uuids = options.delete(:use_uuids) || true
  if delete_all
    updates = docs.map { |doc|
      { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true } }
  else
    updates = docs
  end
  if (use_uuids)
    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
  end
  post("_bulk_docs", {:docs => updates}, headers)
end

#compact(headers = {}) ⇒ Object

Compacts this database.



363
364
365
# File 'lib/cushion/database.rb', line 363

def compact(headers = {})
  post("_compact", nil, headers)
end

#copy(source, destination, options = {}) ⇒ Object

Copies the document at source to a new or existing location at destination. Set the dest_rev option to overwrite an existing document. Set the headers option to pass custom request headers. Example:

db.copy("doc1", "doc2")
  #=> Copies to a new location
db.copy("doc1", "doc3", :dest_rev => "4567")
  #=> Overwrites an existing doc


217
218
219
220
221
222
223
224
225
226
227
# File 'lib/cushion/database.rb', line 217

def copy(source, destination, options = {})
  headers = options.delete(:headers) || {}
  dest_rev = options.delete(:dest_rev)
  slug = Cushion.escape_key(source)
  dest = if dest_rev
    "#{destination}?rev=#{dest_rev}"
  else
    destination
  end
  server.copy("#{@name}/#{slug}", dest, headers)
end

#copy_attachment(id, old_filename, new_filename, options = {}) ⇒ Object

Copies an attachment to a new location. This is presently experimental.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/cushion/database.rb', line 230

def copy_attachment(id, old_filename, new_filename, options = {})
  headers = options.delete(:headers) || {}
  src = fetch(id, old_filename)
  headers[:content_type] = src.headers[:content_type]
  dest_id = options[:dest_id] || id
  opts = options[:dest_rev] ? { :rev => options[:dest_rev] } : {}
  if opts.empty? && (id == dest_id)
    opts = { :rev => src.headers[:etag] }
  end
  res = attach(dest_id, new_filename, src, { :headers => headers }.merge(opts))
  if id == dest_id
    res.merge("src_rev" => res["rev"])
  else
    res.merge("src_rev" => src.headers[:etag])
  end
end

#create(headers = {}) ⇒ Object

Creates this database on the server.



368
369
370
# File 'lib/cushion/database.rb', line 368

def create(headers = {})
  server.put(@name, nil, headers)
end

#delete(path, headers = {}) ⇒ Object

Issues a DELETE request to this database. See Cushion::Server#delete.



433
434
435
# File 'lib/cushion/database.rb', line 433

def delete(path, headers = {})
  server.delete("#{@name}/#{path}", headers)
end

#destroy(*args) ⇒ Object

Deletes a single document or attachment by key. The rev option must be provided.



152
153
154
155
156
157
158
# File 'lib/cushion/database.rb', line 152

def destroy(*args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  headers = options.delete(:headers) || {}
  id, file = args
  slug = Cushion.escape_key(id, file)
  delete("#{slug}?rev=#{options[:rev]}", headers)
end

#doc(id, options = {}) ⇒ Object Also known as: document

Retrieves a single document by id and returns a Cushion::Document linked to this database.



78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/cushion/database.rb', line 78

def doc(id, options = {})
  result = fetch(id.to_s, options)

  ndoc = if /^_design/ =~ result["_id"]
    Design.new(result)
  else
    Document.new(result)
  end

  ndoc.database = self
  ndoc
end

#drop(headers = {}) ⇒ Object

Deletes this database from the server.



373
374
375
# File 'lib/cushion/database.rb', line 373

def drop(headers = {})
  server.delete(@name, headers)
end

#external(verb, process_path, params = {}) ⇒ Object

Issues a request to the CouchDB external server identified by process. Calls to external must include a request verb. Set the headers option to pass custom request headers.



407
408
409
410
# File 'lib/cushion/database.rb', line 407

def external(verb, process_path, params = {})
  path = Cushion.paramify_url("_#{process_path}", params[:query])
  request(verb, path, :body => params[:body], :headers => params[:headers])
end

#fetch(*args) ⇒ Object

Retrieves a single document or attachment by key. Returns attachments as an OpenURI IO object if the use_tempfiles database option has been set. Set the headers option to pass custom request headers. Examples:

db.fetch("mydoc")
db.fetch("my/doc")      # Forward slashes are automatically escaped...
db.fetch("_design/foo") # ...except in the case of design docs

Attachments can be fetched by supplying the id and filename as follows:

db.fetch("mydoc", "foo.txt")
db.fetch("mydoc", "path/to/foo.txt") # Virtual file path

Cushion requests “application/json” by default, and response bodies will automatically be parsed as such. To request data from CouchDB in another (unparsed) format, simply set the accept header:

db.fetch("mydoc", :headers => { :accept => "text/plain" })

Set :head => true to return the headers rather than the content body. Set :etag => some_value to perform a simple if-none-match conditional GET.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/cushion/database.rb', line 47

def fetch(*args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  id, file = args
  slug = Cushion.escape_key(id, file)
  headers = options.delete(:headers) || {}
  if etag = options.delete(:etag)
    headers.merge!(:if_none_match => "\"#{etag}\"")
  end
  head = options.delete(:head)
  path = Cushion.paramify_url("#{slug}", options)
  if head
    return head(path, headers)
  elsif file && @use_tempfiles
    return server.open_attachment("#{@name}/#{path}")
  end
  get(path, headers)
end

#get(path, headers = {}) ⇒ Object

Issues a GET request to this database. See Cushion::Server#get.



418
419
420
# File 'lib/cushion/database.rb', line 418

def get(path, headers = {})
  server.get("#{@name}/#{path}", headers)
end

#head(path, headers = {}) ⇒ Object

Issues a HEAD request to this database. See Cushion::Server#head.



413
414
415
# File 'lib/cushion/database.rb', line 413

def head(path, headers = {})
  server.head("#{@name}/#{path}", headers)
end

#info(headers = {}) ⇒ Object

Retrieves information about this database.



358
359
360
# File 'lib/cushion/database.rb', line 358

def info(headers = {})
  server.get(@name, headers)
end

#key?(*args) ⇒ Boolean Also known as: has_key?

Returns true if a document key exists in this database. See #fetch.

Returns:

  • (Boolean)


66
67
68
69
70
71
72
# File 'lib/cushion/database.rb', line 66

def key?(*args)
  options = args.last.is_a?(Hash) ? args.pop : {}
  id, file = args
  !!fetch(id, file, options.merge(:head => true))
rescue RestClient::ResourceNotFound
  false
end

#list(design, list_template, view, options = {}) ⇒ Object

Query the list template identified by design, list_template and view. The results are not parsed by default. Set the headers option to pass custom request headers.



350
351
352
353
354
355
# File 'lib/cushion/database.rb', line 350

def list(design, list_template, view, options = {})
  defaults = { :accept => "text/html;text/plain;*/*" }
  headers = options.delete(:headers) || {}
  path = Cushion.paramify_url("_list/#{design}/#{list_template}/#{view}", options)
  get(path, defaults.merge(headers))
end

#move(source, destination, options = {}) ⇒ Object

Moves the document at source to a new or existing location at destination. Set the dest_rev option to overwrite an existing document. Set the headers option to pass custom request headers. Example:

db.move("doc1", "doc2", :rev => "1234")
  #=> Moves to a new location
db.copy("doc1", "doc3", :rev => "1234", :dest_rev => "4567")
  #=> Overwrites an existing doc


256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/cushion/database.rb', line 256

def move(source, destination, options = {})
  headers = options.delete(:headers) || {}
  dest_rev = options.delete(:dest_rev)
  slug = Cushion.escape_key(source)
  path = Cushion.paramify_url("#{slug}", options)
  dest = if dest_rev
    "#{destination}?rev=#{dest_rev}"
  else
    destination
  end
  server.move("#{@name}/#{path}", dest, headers)
end

#move_attachment(id, old_filename, new_filename, options = {}) ⇒ Object Also known as: rename_attachment

Moves an attachment to a new location. This is presently experimental.



270
271
272
273
274
275
276
277
# File 'lib/cushion/database.rb', line 270

def move_attachment(id, old_filename, new_filename, options = {})
  res = copy_attachment(id, old_filename, new_filename, options)
  if res["ok"]
    destroy(id, old_filename, :rev => res["src_rev"])
  else
    res
  end
end

#post(path, body, headers = {}) ⇒ Object

Issues a POST request to this database. See Cushion::Server#post.



423
424
425
# File 'lib/cushion/database.rb', line 423

def post(path, body, headers = {})
  server.post("#{@name}/#{path}", body, headers)
end

#purge(doc_revs, options = {}) ⇒ Object

Completely purges the supplied document revisions from the database. doc_revs is a hash of document ids, each containing an array of revisions to be deleted. Example:

 doc_revs = {
   "1" => ["12345", "42836"],
   "2" => ["572654"],
   "3" => ["34462"]
 }

db.purge(doc_revs)


399
400
401
# File 'lib/cushion/database.rb', line 399

def purge(doc_revs, options = {})
  post("_purge", doc_revs, options)
end

#put(path, body, headers = {}) ⇒ Object

Issues a PUT request to this database. See Cushion::Server#put.



428
429
430
# File 'lib/cushion/database.rb', line 428

def put(path, body, headers = {})
  server.put("#{@name}/#{path}", body, headers)
end

#recreateObject

Deletes and recreates this database.



378
379
380
381
382
383
384
385
# File 'lib/cushion/database.rb', line 378

def recreate
  begin
    drop
  rescue RestClient::ResourceNotFound
    nil
  end
  create
end

#request(verb, path, params) ⇒ Object

Issues a generic request to this database. See Cushion::Server#request.



438
439
440
# File 'lib/cushion/database.rb', line 438

def request(verb, path, params)
  server.request(verb, "#{@name}/#{path}", params)
end

#show(design, show_template, id, options = {}) ⇒ Object

Query the show template identified by design, show_template and id. The results are not parsed by default. Set the headers option to pass custom request headers.



338
339
340
341
342
343
344
# File 'lib/cushion/database.rb', line 338

def show(design, show_template, id, options = {})
  defaults = { :accept => "text/html;text/plain;*/*" }
  headers = options.delete(:headers) || {}
  slug = Cushion.escape_key(id)
  path = Cushion.paramify_url("_show/#{design}/#{show_template}/#{slug}", options)
  get(path, defaults.merge(headers))
end

#store(doc, options = {}) ⇒ Object

Stores a document to the database. Pass a hash with the desired attributes. If an _id attribute is not provided one will be generated automatically from the server UUID cache. Examples:

db.store("foo" => "bar")  #=> Creates a doc with an auto-generated key
db.store("_id" => "def")  #=> Creates a doc with key "def"

To update a document, set the _rev attribute on the document hash:

db.store("_id" => "ghi", "_rev" => "1234")

Inline attachments are automatically encoded within the document.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/cushion/database.rb', line 105

def store(doc, options = {})
  headers = options.delete(:headers) || {}
  if doc['_attachments']
    doc['_attachments'] = Cushion.encode_attachments(doc['_attachments'])
  end
  res = if doc['_id']
    slug = Cushion.escape_key(doc['_id'])
    put("#{slug}", doc, headers)
  else
    slug = doc['_id'] = server.next_uuid
    put("#{slug}", doc, headers)
  end
  if res['ok']
    doc['_id'] = res['id']
    doc['_rev'] = res['rev']
  end
  res
end

#temp(funcs, options = {}) ⇒ Object

Convenience method for querying temporary views. See #view for more information.



330
331
332
# File 'lib/cushion/database.rb', line 330

def temp(funcs, options = {})
  view(:temp, options.merge(:funcs => funcs))
end

#view(name, options = {}) ⇒ Object

Query a named view. Set name to :all to query CouchDB’s all_docs view. Set name to :temp to query a temp_view. Set the keys option to perform a key-based multi-document fetch against any view. Examples

temp_view = { :map => "function(doc){emit(doc.status,null)}" }
db.view(:temp, :funcs => temp_view, :key => "verified")
db.view("people/by_first_name", :key => "gomer")
db.view("foo/bar", :keys => ["a", "b", "c"])
db.view(:all)

Set the headers option to pass custom request headers. Set :etag => some_value to perform a simple if-none-match conditional GET.

Temp view queries are slow, so they should only be used as a convenience during development. Whenever possible you should query against saved views.



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/cushion/database.rb', line 298

def view(name, options = {})
  keys = options.delete(:keys)
  headers = options.delete(:headers) || {}
  if etag = options.delete(:etag)
    headers.merge!(:if_none_match => "\"#{etag}\"".squeeze("\""))
  end
  body = keys ? {:keys => keys} : {}
  if name == :all
    slug = "_all_docs"
  elsif name == :temp
    slug = "_temp_view"
    body.merge!(options.delete(:funcs))
  else
    slug = "_view/#{name}"
  end
  path = Cushion.paramify_url(slug, options)

  if body.any?
    post(path, body, headers)
  else
    get(path, headers)
  end
end