diff --git a/lib/couchrest/model/design_doc.rb b/lib/couchrest/model/design_doc.rb index b634351..36c3940 100644 --- a/lib/couchrest/model/design_doc.rb +++ b/lib/couchrest/model/design_doc.rb @@ -7,7 +7,11 @@ module CouchRest module ClassMethods def design_doc - @design_doc ||= ::CouchRest::Design.new(default_design_doc) + @design_doc ||= if auto_update_design_doc + ::CouchRest::Design.new(default_design_doc) + else + stored_design_doc || ::CouchRest::Design.new(default_design_doc) + end end def design_doc_id @@ -75,16 +79,25 @@ module CouchRest # If auto updates enabled, check checksum cache return design_doc if auto_update_design_doc && design_doc_cache_checksum(db) == checksum - # Load up the stored doc (if present), update, and save - saved = stored_design_doc(db) - if saved - if force || saved['couchrest-hash'] != checksum - saved.merge!(design_doc) - db.save_doc(saved) + retries = 1 + begin + # Load up the stored doc (if present), update, and save + saved = stored_design_doc(db) + if saved + if force || saved['couchrest-hash'] != checksum + saved.merge!(design_doc) + db.save_doc(saved) + @design_doc = saved # update memo to point to the document we actually saved + end + else + design_doc.delete('_rev') # This is a new document and so doesn't have a revision yet + db.save_doc(design_doc) end - else - db.save_doc(design_doc) - design_doc.delete('_rev') # Prevent conflicts, never store rev as DB specific + rescue RestClient::Conflict + # if we get a conflict retry the operation... + raise if retries < 1 + retries -= 1 + retry end # Ensure checksum cached for next attempt if using auto updates diff --git a/lib/couchrest/model/designs.rb b/lib/couchrest/model/designs.rb index e17f1bc..3862567 100644 --- a/lib/couchrest/model/designs.rb +++ b/lib/couchrest/model/designs.rb @@ -58,10 +58,11 @@ module CouchRest self.model = model end - # Define a view and generate a method that will provide a new - # View instance when requested. + # Generate a method that will provide a new View instance when + # requested. This will also define the view in CouchDB unless + # auto_update_design_doc is disabled. def view(name, opts = {}) - View.create(model, name, opts) + View.create(model, name, opts) if model.auto_update_design_doc create_view_method(name) end diff --git a/lib/couchrest/model/views.rb b/lib/couchrest/model/views.rb index 89dffbd..f8331e5 100644 --- a/lib/couchrest/model/views.rb +++ b/lib/couchrest/model/views.rb @@ -72,9 +72,12 @@ module CouchRest # spec/couchrest/more/extended_doc_spec.rb. def view_by(*keys) + return unless auto_update_design_doc + opts = keys.pop if keys.last.is_a?(Hash) opts ||= {} ducktype = opts.delete(:ducktype) + unless ducktype || opts[:map] opts[:guards] ||= [] opts[:guards].push "(doc['#{model_type_key}'] == '#{self.to_s}')" diff --git a/spec/unit/design_doc_spec.rb b/spec/unit/design_doc_spec.rb index d034d68..299f5e6 100644 --- a/spec/unit/design_doc_spec.rb +++ b/spec/unit/design_doc_spec.rb @@ -159,39 +159,68 @@ describe CouchRest::Model::DesignDoc do end describe "when auto_update_design_doc false" do - - before :all do - Article.auto_update_design_doc = false - Article.save_design_doc! - end + # We really do need a new class for each of example. If we try + # to use the same class the examples interact with each in ways + # that can hide failures because the design document gets cached + # at the class level. + let(:model_class) { + class_name = "#{example.metadata[:full_description].gsub(/\s+/,'_').camelize}Model" + doc = CouchRest::Document.new("_id" => "_design/#{class_name}") + doc["language"] = "javascript" + doc["views"] = {"all" => {"map" => + "function(doc) { + if (doc['type'] == 'Article') { + emit(doc['_id'],1); + } + }"}, + "by_name" => {"map" => + "function(doc) { + if ((doc['type'] == '#{class_name}') && (doc['name'] != null)) { + emit(doc['name'], null); + }", + "reduce" => + "function(keys, values, rereduce) { + return sum(values); + }"}} + + DB.save_doc doc - after :all do - Article.auto_update_design_doc = true - end + eval <<-KLASS + class ::#{class_name} < CouchRest::Model::Base + use_database DB + self.auto_update_design_doc = false + design do + view :by_name + end + property :name, String + end + KLASS - it "will not send a request for the saved design doc" do - Article.should_not_receive(:stored_design_doc) - Article.by_date - end + class_name.constantize + } it "will not update stored design doc if view changed" do - Article.by_date - orig = Article.stored_design_doc - design = Article.design_doc - view = design['views']['by_date']['map'] - design['views']['by_date']['map'] = view + ' ' - Article.by_date - Article.stored_design_doc['_rev'].should eql(orig['_rev']) + model_class.by_name + orig = model_class.stored_design_doc + design = model_class.design_doc + view = design['views']['by_name']['map'] + design['views']['by_name']['map'] = view + ' ' + model_class.by_name + model_class.stored_design_doc['_rev'].should eql(orig['_rev']) end it "will update stored design if forced" do - Article.by_date - orig = Article.stored_design_doc - design = Article.design_doc - view = design['views']['by_date']['map'] - design['views']['by_date']['map'] = view + ' ' - Article.save_design_doc! - Article.stored_design_doc['_rev'].should_not eql(orig['_rev']) + model_class.by_name + orig = model_class.stored_design_doc + design = model_class.design_doc + view = design['views']['by_name']['map'] + design['views']['by_name']['map'] = view + ' ' + model_class.save_design_doc! + model_class.stored_design_doc['_rev'].should_not eql(orig['_rev']) + end + + it "is able to use predefined views" do + model_class.by_name(key: "special").all end end end diff --git a/spec/unit/designs_spec.rb b/spec/unit/designs_spec.rb index e4e981d..96d02a3 100644 --- a/spec/unit/designs_spec.rb +++ b/spec/unit/designs_spec.rb @@ -83,7 +83,23 @@ describe CouchRest::Model::Designs do @object.should_receive(:create_view_method).with('test') @object.view('test') end + end + context "for model with auto_update_design_doc disabled " do + class ::DesignModelAutoUpdateDesignDocDisabled < ::CouchRest::Model::Base + self.auto_update_design_doc = false + end + + describe "#view" do + before :each do + @object = @klass.new(DesignModelAutoUpdateDesignDocDisabled) + end + + it "does not attempt to create view" do + CouchRest::Model::Designs::View.should_not_receive(:create) + @object.view('test') + end + end end describe "#filter" do