Multiple database support for ExtendedDocument.

New optional parameters are available to select the database:

Mixins::DocumentQueries
  * get <id>, <db>
  * all :database => <db>
  * first :database => <db>

Mixins::Views
  * view <name>, :database => <db>
  * all_design_doc_versions <db>
  * cleanup_design_docs! <db>

Mixins::DesignDoc
  * refresh_design_doc now only updates the design_doc _id and removes _rev
  * call save_design_doc to save and update the design_doc
  * call save_design_doc_on <db> to save the design doc on a given
    database without modifying the model's design_doc object

Design (core/design.rb)
  * new method view_on <db>, ...

Bug fixes:
  * design_doc_slug in mixins/design_doc.rb was using an empty document
    to calculate the slug each time
  * method_missing in core/extended_document.rb now passes a block through
This commit is contained in:
Brian Candler 2009-03-27 11:27:37 +00:00
parent f9278a4ca6
commit ec7848b783
7 changed files with 158 additions and 55 deletions

View file

@ -19,7 +19,7 @@ module CouchRest
def design_doc_slug
return design_doc_slug_cache if (design_doc_slug_cache && design_doc_fresh)
funcs = []
design_doc ||= Design.new(default_design_doc)
self.design_doc ||= Design.new(default_design_doc)
design_doc['views'].each do |name, view|
funcs << "#{name}/#{view['map']}#{view['reduce']}"
end
@ -43,21 +43,42 @@ module CouchRest
end
def refresh_design_doc
did = design_doc_id
saved = database.get(did) rescue nil
design_doc['_id'] = design_doc_id
design_doc.delete('_rev')
#design_doc.database = nil
self.design_doc_fresh = true
end
# Save the design doc onto the default database, and update the
# design_doc attribute
def save_design_doc
refresh_design_doc unless design_doc_fresh
self.design_doc = update_design_doc(design_doc)
end
# Save the design doc onto a target database in a thread-safe way,
# not modifying the model's design_doc
def save_design_doc_on(db)
update_design_doc(Design.new(design_doc), db)
end
private
# Writes out a design_doc to a given database, returning the
# updated design doc
def update_design_doc(design_doc, db = database)
saved = db.get(design_doc['_id']) rescue nil
if saved
design_doc['views'].each do |name, view|
saved['views'][name] = view
end
database.save_doc(saved)
self.design_doc = saved
db.save_doc(saved)
saved
else
design_doc['_id'] = did
design_doc.delete('_rev')
design_doc.database = database
design_doc.database = db
design_doc.save
design_doc
end
self.design_doc_fresh = true
end
end # module ClassMethods

View file

@ -36,8 +36,8 @@ module CouchRest
end
# Load a document from the database by id
def get(id)
doc = database.get id
def get(id, db = database)
doc = db.get id
new(doc)
end

View file

@ -54,6 +54,10 @@ module CouchRest
# themselves. By default <tt>Post.by_date</tt> will return the
# documents included in the generated view.
#
# Calling with :database => [instance of CouchRest::Database] will
# send the query to a specific database, otherwise it will go to
# the model's default database (use_database)
#
# CouchRest::Database#view options can be applied at view definition
# time as defaults, and they will be curried and used at view query
# time. Or they can be overridden at query time.
@ -67,7 +71,7 @@ module CouchRest
# that model won't be available until generation is complete. This can
# take some time with large databases. Strategies are in the works.
#
# To understand the capabilities of this view system more compeletly,
# To understand the capabilities of this view system more completely,
# it is recommended that you read the RSpec file at
# <tt>spec/core/model_spec.rb</tt>.
@ -97,12 +101,13 @@ module CouchRest
refresh_design_doc
end
query[:raw] = true if query[:reduce]
db = query.delete(:database) || database
raw = query.delete(:raw)
fetch_view_with_docs(name, query, raw, &block)
fetch_view_with_docs(db, name, query, raw, &block)
end
def all_design_doc_versions
database.documents :startkey => "_design/#{self.to_s}-",
def all_design_doc_versions(db = database)
db.documents :startkey => "_design/#{self.to_s}-",
:endkey => "_design/#{self.to_s}-\u9999"
end
@ -111,11 +116,11 @@ module CouchRest
# and consistently using the latest code, is the way to clear out old design
# docs. Running it to early could mean that live code has to regenerate
# potentially large indexes.
def cleanup_design_docs!
ddocs = all_design_doc_versions
def cleanup_design_docs!(db = database)
ddocs = all_design_doc_versions(db)
ddocs["rows"].each do |row|
if (row['id'] != design_doc_id)
database.delete_doc({
db.delete_doc({
"_id" => row['id'],
"_rev" => row['value']['rev']
})
@ -125,30 +130,31 @@ module CouchRest
private
def fetch_view_with_docs(name, opts, raw=false, &block)
def fetch_view_with_docs(db, name, opts, raw=false, &block)
if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
fetch_view(name, opts, &block)
fetch_view(db, name, opts, &block)
else
begin
view = fetch_view name, opts.merge({:include_docs => true}), &block
view = fetch_view db, name, opts.merge({:include_docs => true}), &block
view['rows'].collect{|r|new(r['doc'])} if view['rows']
rescue
# fallback for old versions of couchdb that don't
# have include_docs support
view = fetch_view(name, opts, &block)
view = fetch_view(db, name, opts, &block)
view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
end
end
end
def fetch_view view_name, opts, &block
def fetch_view(db, view_name, opts, &block)
raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless db
retryable = true
begin
design_doc.view(view_name, opts, &block)
# the design doc could have been deleted by a rogue process
design_doc.view_on(db, view_name, opts, &block)
# the design doc may not have been saved yet on this database
rescue RestClient::ResourceNotFound => e
if retryable
refresh_design_doc
save_design_doc_on(db)
retryable = false
retry
else