diff --git a/lib/couchrest/core/design.rb b/lib/couchrest/core/design.rb
index f9b8e22..e9d1146 100644
--- a/lib/couchrest/core/design.rb
+++ b/lib/couchrest/core/design.rb
@@ -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
diff --git a/lib/couchrest/mixins/design_doc.rb b/lib/couchrest/mixins/design_doc.rb
index 510d145..0251815 100644
--- a/lib/couchrest/mixins/design_doc.rb
+++ b/lib/couchrest/mixins/design_doc.rb
@@ -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
diff --git a/lib/couchrest/mixins/document_queries.rb b/lib/couchrest/mixins/document_queries.rb
index 01d97bf..ff05ee2 100644
--- a/lib/couchrest/mixins/document_queries.rb
+++ b/lib/couchrest/mixins/document_queries.rb
@@ -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
diff --git a/lib/couchrest/mixins/views.rb b/lib/couchrest/mixins/views.rb
index 61dccb3..f6d8b7f 100644
--- a/lib/couchrest/mixins/views.rb
+++ b/lib/couchrest/mixins/views.rb
@@ -54,6 +54,10 @@ module CouchRest
# themselves. By default Post.by_date 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
# spec/core/model_spec.rb.
@@ -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
diff --git a/lib/couchrest/more/extended_document.rb b/lib/couchrest/more/extended_document.rb
index a103b62..62c26f8 100644
--- a/lib/couchrest/more/extended_document.rb
+++ b/lib/couchrest/more/extended_document.rb
@@ -77,10 +77,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
diff --git a/spec/couchrest/core/design_spec.rb b/spec/couchrest/core/design_spec.rb
index af1fb01..186e013 100644
--- a/spec/couchrest/core/design_spec.rb
+++ b/spec/couchrest/core/design_spec.rb
@@ -57,6 +57,13 @@ describe CouchRest::Design do
res = @des.view :by_name
res["rows"][0]["key"].should == "x"
end
+ it "should be queryable on specified database" do
+ @des.name = "mydesign"
+ @des.save
+ @des.database = nil
+ res = @des.view_on @db, :by_name
+ res["rows"][0]["key"].should == "x"
+ end
end
describe "from a saved document" do
diff --git a/spec/couchrest/more/extended_doc_view_spec.rb b/spec/couchrest/more/extended_doc_view_spec.rb
index 4a3e6ee..171c81f 100644
--- a/spec/couchrest/more/extended_doc_view_spec.rb
+++ b/spec/couchrest/more/extended_doc_view_spec.rb
@@ -3,6 +3,13 @@ require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
describe "ExtendedDocument views" do
+
+ class Unattached < CouchRest::ExtendedDocument
+ # Note: no use_database here
+ property :title
+ property :questions
+ view_by :title
+ end
describe "a model with simple views and a default param" do
before(:all) do
@@ -75,12 +82,18 @@ describe "ExtendedDocument views" do
end
it "should yield" do
courses = []
- rs = Course.by_title # remove me
Course.view(:by_title) do |course|
courses << course
end
courses[0]["doc"]["title"].should =='aaa'
end
+ it "should yield with by_key method" do
+ courses = []
+ Course.by_title do |course|
+ courses << course
+ end
+ courses[0]["doc"]["title"].should =='aaa'
+ end
end
@@ -103,6 +116,76 @@ describe "ExtendedDocument views" do
end
end
+ describe "a model class not tied to a database" do
+ before(:all) do
+ reset_test_db!
+ @db = TEST_SERVER.default_database
+ %w{aaa bbb ddd eee}.each do |title|
+ u = Unattached.new(:title => title)
+ u.database = @db
+ u.save
+ @first_id ||= u.id
+ end
+ end
+ it "should barf on all if no database given" do
+ lambda{Unattached.all}.should raise_error
+ end
+ it "should query all" do
+ rs = Unattached.all :database=>@db
+ rs.length.should == 4
+ end
+ it "should barf on query if no database given" do
+ lambda{Unattached.view :by_title}.should raise_error
+ end
+ it "should make the design doc upon first query" do
+ Unattached.by_title :database=>@db
+ doc = Unattached.design_doc
+ doc['views']['all']['map'].should include('Unattached')
+ end
+ it "should merge query params" do
+ rs = Unattached.by_title :database=>@db, :startkey=>"bbb", :endkey=>"eee"
+ rs.length.should == 3
+ end
+ it "should query via view" do
+ view = Unattached.view :by_title, :database=>@db
+ designed = Unattached.by_title :database=>@db
+ view.should == designed
+ end
+ it "should yield" do
+ things = []
+ Unattached.view(:by_title, :database=>@db) do |thing|
+ things << thing
+ end
+ things[0]["doc"]["title"].should =='aaa'
+ end
+ it "should barf on get if no database given" do
+ lambda{Unattached.get("aaa")}.should raise_error
+ end
+ it "should get from specific database" do
+ u = Unattached.get(@first_id, @db)
+ u.title.should == "aaa"
+ end
+ it "should barf on first if no database given" do
+ lambda{Unattached.first}.should raise_error
+ end
+ it "should get first" do
+ u = Unattached.first :database=>@db
+ u.title.should =~ /\A...\z/
+ end
+ it "should barf on all_design_doc_versions if no database given" do
+ lambda{Unattached.all_design_doc_versions}.should raise_error
+ end
+ it "should clean up design docs left around on specific database" do
+ Unattached.by_title :database=>@db
+ Unattached.all_design_doc_versions(@db)["rows"].length.should == 1
+ Unattached.view_by :questions
+ Unattached.by_questions :database=>@db
+ Unattached.all_design_doc_versions(@db)["rows"].length.should == 2
+ Unattached.cleanup_design_docs!(@db)
+ Unattached.all_design_doc_versions(@db)["rows"].length.should == 1
+ end
+ end
+
describe "a model with a compound key view" do
before(:all) do
Article.design_doc_fresh = false
@@ -177,14 +260,11 @@ describe "ExtendedDocument views" do
newdocs["rows"].length.should == @design_docs["rows"].length
end
it "should create a new version of the design document on view access" do
- old_design_doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
+ ddocs = Article.all_design_doc_versions["rows"].length
Article.view_by :updated_at
Article.by_updated_at
- newdocs = Article.database.documents({:startkey => "_design/", :endkey => "_design/\u9999"})
-
- doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
- doc["_rev"].should_not == old_design_doc["_rev"]
- doc["views"].keys.should include("by_updated_at")
+ Article.all_design_doc_versions["rows"].length.should == ddocs + 1
+ Article.design_doc["views"].keys.should include("by_updated_at")
end
end
@@ -196,12 +276,11 @@ describe "ExtendedDocument views" do
Article.by_field
end
it "should clean them up" do
- ddocs = Article.all_design_doc_versions
Article.view_by :stream
Article.by_stream
+ Article.all_design_doc_versions["rows"].length.should > 1
Article.cleanup_design_docs!
- ddocs = Article.all_design_doc_versions
- ddocs["rows"].length.should == 1
+ Article.all_design_doc_versions["rows"].length.should == 1
end
end
end