Merge commit 'candlerb/candlerb/design-doc' into candlerb-merge
* commit 'candlerb/candlerb/design-doc': Update handler for old versions of couchdb ClassProxy provides class-level methods on a dynamically chosen database. Remove obsolete 'move' methods Tidying up spec, remove unnecessary assignments to local variable Multiple database support for ExtendedDocument. Typo in comment Move design_doc attributes to Mixins::DesignDoc
This commit is contained in:
commit
4337e676ee
11 changed files with 347 additions and 80 deletions
|
@ -35,11 +35,17 @@ JAVASCRIPT
|
|||
end
|
||||
|
||||
# Dispatches to any named view.
|
||||
# (using the database where this design doc was saved)
|
||||
def view view_name, query={}, &block
|
||||
view_on database, view_name, query, &block
|
||||
end
|
||||
|
||||
# Dispatches to any named view in a specific database
|
||||
def view_on db, view_name, query={}, &block
|
||||
view_name = view_name.to_s
|
||||
view_slug = "#{name}/#{view_name}"
|
||||
defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
|
||||
fetch_view(view_slug, defaults.merge(query), &block)
|
||||
db.view(view_slug, defaults.merge(query), &block)
|
||||
end
|
||||
|
||||
def name
|
||||
|
@ -64,22 +70,6 @@ JAVASCRIPT
|
|||
(self['views'][view]["couchrest-defaults"]||{})
|
||||
end
|
||||
|
||||
# def fetch_view_with_docs name, opts, raw=false, &block
|
||||
# if raw
|
||||
# fetch_view name, opts, &block
|
||||
# else
|
||||
# begin
|
||||
# view = fetch_view 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['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
def fetch_view view_name, opts, &block
|
||||
database.view(view_name, opts, &block)
|
||||
end
|
||||
|
|
|
@ -65,15 +65,6 @@ module CouchRest
|
|||
result['ok']
|
||||
end
|
||||
|
||||
# moves the document to a new id. If the destination id currently exists, a rev must be provided.
|
||||
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
||||
# hash with a '_rev' key
|
||||
def move(dest)
|
||||
raise ArgumentError, "doc.database required to copy" unless database
|
||||
result = database.move_doc(self, dest)
|
||||
result['ok']
|
||||
end
|
||||
|
||||
# Returns the CouchDB uri for the document
|
||||
def uri(append_rev = false)
|
||||
return nil if new_document?
|
||||
|
|
108
lib/couchrest/mixins/class_proxy.rb
Normal file
108
lib/couchrest/mixins/class_proxy.rb
Normal file
|
@ -0,0 +1,108 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module ClassProxy
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Return a proxy object which represents a model class on a
|
||||
# chosen database instance. This allows you to DRY operations
|
||||
# where a database is chosen dynamically.
|
||||
#
|
||||
# ==== Example:
|
||||
#
|
||||
# db = CouchRest::Database.new(...)
|
||||
# articles = Article.on(db)
|
||||
#
|
||||
# articles.all { ... }
|
||||
# articles.by_title { ... }
|
||||
#
|
||||
# u = articles.get("someid")
|
||||
#
|
||||
# u = articles.new(:title => "I like plankton")
|
||||
# u.save # saved on the correct database
|
||||
|
||||
def on(database)
|
||||
Proxy.new(self, database)
|
||||
end
|
||||
end
|
||||
|
||||
class Proxy #:nodoc:
|
||||
def initialize(klass, database)
|
||||
@klass = klass
|
||||
@database = database
|
||||
end
|
||||
|
||||
# ExtendedDocument
|
||||
|
||||
def new(*args)
|
||||
doc = @klass.new(*args)
|
||||
doc.database = @database
|
||||
doc
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
if has_view?(m)
|
||||
query = args.shift || {}
|
||||
view(m, query, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Mixins::DocumentQueries
|
||||
|
||||
def all(opts = {}, &block)
|
||||
@klass.all({:database => @database}.merge(opts), &block)
|
||||
end
|
||||
|
||||
def first(opts = {})
|
||||
@klass.first({:database => @database}.merge(opts))
|
||||
end
|
||||
|
||||
def get(id)
|
||||
@klass.get(id, @database)
|
||||
end
|
||||
|
||||
# Mixins::Views
|
||||
|
||||
def has_view?(view)
|
||||
@klass.has_view?(view)
|
||||
end
|
||||
|
||||
def view(name, query={}, &block)
|
||||
@klass.view(name, {:database => @database}.merge(query), &block)
|
||||
end
|
||||
|
||||
def all_design_doc_versions
|
||||
@klass.all_design_doc_versions(@database)
|
||||
end
|
||||
|
||||
def cleanup_design_docs!
|
||||
@klass.cleanup_design_docs!(@database)
|
||||
end
|
||||
|
||||
# Mixins::DesignDoc
|
||||
|
||||
def design_doc
|
||||
@klass.design_doc
|
||||
end
|
||||
|
||||
def design_doc_fresh
|
||||
@klass.design_doc_fresh
|
||||
end
|
||||
|
||||
def refresh_design_doc
|
||||
@klass.refresh_design_doc
|
||||
end
|
||||
|
||||
def save_design_doc
|
||||
@klass.save_design_doc_on(@database)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,6 +6,9 @@ module CouchRest
|
|||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.send(:extlib_inheritable_accessor, :design_doc)
|
||||
base.send(:extlib_inheritable_accessor, :design_doc_slug_cache)
|
||||
base.send(:extlib_inheritable_accessor, :design_doc_fresh)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -16,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
|
||||
|
@ -40,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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@ require File.join(File.dirname(__FILE__), 'document_queries')
|
|||
require File.join(File.dirname(__FILE__), 'views')
|
||||
require File.join(File.dirname(__FILE__), 'design_doc')
|
||||
require File.join(File.dirname(__FILE__), 'validation')
|
||||
require File.join(File.dirname(__FILE__), 'extended_attachments')
|
||||
require File.join(File.dirname(__FILE__), 'extended_attachments')
|
||||
require File.join(File.dirname(__FILE__), 'class_proxy')
|
||||
|
|
|
@ -4,9 +4,6 @@ module CouchRest
|
|||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.send(:extlib_inheritable_accessor, :design_doc)
|
||||
base.send(:extlib_inheritable_accessor, :design_doc_slug_cache)
|
||||
base.send(:extlib_inheritable_accessor, :design_doc_fresh)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
@ -57,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.
|
||||
|
@ -70,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>.
|
||||
|
||||
|
@ -100,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
|
||||
|
||||
|
@ -114,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']
|
||||
})
|
||||
|
@ -128,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['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
|
||||
view = fetch_view(db, name, opts, &block)
|
||||
view['rows'].collect{|r|new(db.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 rouge 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
|
||||
|
|
|
@ -56,12 +56,6 @@ module RestClient
|
|||
:url => url,
|
||||
:headers => headers)
|
||||
end
|
||||
|
||||
def self.move(url, headers={})
|
||||
Request.execute(:method => :move,
|
||||
:url => url,
|
||||
:headers => headers)
|
||||
end
|
||||
|
||||
# class Request
|
||||
#
|
||||
|
|
|
@ -11,6 +11,7 @@ module CouchRest
|
|||
include CouchRest::Mixins::Views
|
||||
include CouchRest::Mixins::DesignDoc
|
||||
include CouchRest::Mixins::ExtendedAttachments
|
||||
include CouchRest::Mixins::ClassProxy
|
||||
|
||||
def self.inherited(subklass)
|
||||
subklass.send(:include, CouchRest::Mixins::Properties)
|
||||
|
@ -77,10 +78,10 @@ module CouchRest
|
|||
end
|
||||
|
||||
# Temp solution to make the view_by methods available
|
||||
def self.method_missing(m, *args)
|
||||
def self.method_missing(m, *args, &block)
|
||||
if has_view?(m)
|
||||
query = args.shift || {}
|
||||
view(m, query, *args)
|
||||
view(m, query, *args, &block)
|
||||
else
|
||||
super
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue