From 3f1b2ea0c63db643447cf5ea6306df26c950da7f Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Tue, 19 Jul 2011 18:03:31 +0200 Subject: [PATCH 1/9] Casting array type properties now possible --- history.md | 5 +++++ lib/couchrest/model/property.rb | 3 +-- spec/unit/property_spec.rb | 15 +++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/history.md b/history.md index e1c8329..26bdae8 100644 --- a/history.md +++ b/history.md @@ -1,5 +1,10 @@ # CouchRest Model Change History +## 1.1.2 - 2011-07-XX + +* Minor fix + * Removing restriction that prohibited objects that cast as an array to be loaded. + ## 1.1.1 - 2011-07-04 * Minor fix diff --git a/lib/couchrest/model/property.rb b/lib/couchrest/model/property.rb index f303b07..6058fda 100644 --- a/lib/couchrest/model/property.rb +++ b/lib/couchrest/model/property.rb @@ -43,9 +43,8 @@ module CouchRest::Model end end - # Cast an individual value, not an array + # Cast an individual value def cast_value(parent, value) - raise "An array inside an array cannot be casted, use Embeddable module" if value.is_a?(Array) value = typecast_value(value, self) associate_casted_value_to_parent(parent, value) end diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index b799908..49548e9 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -442,15 +442,18 @@ describe "Property Class" do ary.last.should eql(Date.new(2011, 05, 22)) end - it "should raise and error if value is array when type is not" do - property = CouchRest::Model::Property.new(:test, Date) + it "should cast an object that provides an array" do + prop = Class.new do + attr_accessor :ary + def initialize(val); self.ary = val; end + def as_json; ary; end + end + property = CouchRest::Model::Property.new(:test, prop) parent = mock("FooClass") - lambda { - cast = property.cast(parent, [Date.new(2010, 6, 1)]) - }.should raise_error + cast = property.cast(parent, [1, 2]) + cast.ary.should eql([1, 2]) end - it "should set parent as casted_by object in CastedArray" do property = CouchRest::Model::Property.new(:test, [Object]) parent = mock("FooObject") From 3258ac22e99c7e3e90fc9623c5d0193e24f04f80 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Tue, 19 Jul 2011 21:28:44 +0200 Subject: [PATCH 2/9] updating to couchrest 1.1.2 and as_couch_json method --- couchrest_model.gemspec | 2 +- history.md | 6 ++++++ lib/couchrest/model/properties.rb | 6 ++++-- spec/unit/property_spec.rb | 14 +++++++++++--- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/couchrest_model.gemspec b/couchrest_model.gemspec index 8e7e315..4c873e8 100644 --- a/couchrest_model.gemspec +++ b/couchrest_model.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |s| s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] - s.add_dependency(%q, "1.1.1") + s.add_dependency(%q, "~> 1.1.2") s.add_dependency(%q, "~> 1.15") s.add_dependency(%q, "~> 3.0") s.add_dependency(%q, "~> 0.3.22") diff --git a/history.md b/history.md index e1c8329..387f171 100644 --- a/history.md +++ b/history.md @@ -1,5 +1,11 @@ # CouchRest Model Change History +## 1.1.2 - 2011-07-XX + +* Minor fixes + * Upgrade to couchrest 1.1.2 + * Override as_couch_json to ensure nil values not stored + ## 1.1.1 - 2011-07-04 * Minor fix diff --git a/lib/couchrest/model/properties.rb b/lib/couchrest/model/properties.rb index 7b2c494..54cfb71 100644 --- a/lib/couchrest/model/properties.rb +++ b/lib/couchrest/model/properties.rb @@ -12,8 +12,10 @@ module CouchRest raise "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (method_defined?(:[]) && method_defined?(:[]=)) end - def as_json(options = nil) - Hash[self].reject{|k,v| v.nil?}.as_json(options) + # Provide an attribute hash ready to be sent to CouchDB but with + # all the nil attributes removed. + def as_couch_json + super.delete_if{|k,v| v.nil?} end # Returns the Class properties with their values diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb index b799908..790beb4 100644 --- a/spec/unit/property_spec.rb +++ b/spec/unit/property_spec.rb @@ -62,15 +62,23 @@ describe CouchRest::Model::Property do @card.updated_at.should_not be_nil end - describe "#as_json" do + describe "#as_couch_json" do it "should provide a simple hash from model" do - @card.as_json.class.should eql(Hash) + @card.as_couch_json.class.should eql(Hash) end it "should remove properties from Hash if value is nil" do @card.last_name = nil - @card.as_json.keys.include?('last_name').should be_false + @card.as_couch_json.keys.include?('last_name').should be_false + end + + end + + describe "#as_json" do + + it "should provide a simple hash from model" do + @card.as_json.class.should eql(Hash) end it "should pass options to Active Support's as_json" do From 72e3ac37d65b905d7d405d1fe8a32c799fdae07f Mon Sep 17 00:00:00 2001 From: Kim Burgestrand Date: Sun, 31 Jul 2011 04:40:31 +0200 Subject: [PATCH 3/9] Add CouchRest::Model::Base.respond_to_missing? and respond_to? --- lib/couchrest/model/base.rb | 15 +++++++++++++++ spec/unit/view_spec.rb | 17 ++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/couchrest/model/base.rb b/lib/couchrest/model/base.rb index ed46699..40cb968 100644 --- a/lib/couchrest/model/base.rb +++ b/lib/couchrest/model/base.rb @@ -82,6 +82,21 @@ module CouchRest super end + # compatbility for 1.8, it does not use respond_to_missing? + # thing is, when using it like this only, doing method(:find_by_view) + # will throw an error + def self.respond_to?(m, include_private = false) + super || respond_to_missing?(m, include_private) + end + + # ruby 1.9 feature + # this allows ruby to know that the method is defined using + # method_missing, and as such, method(:find_by_view) will actually + # give a Method back, and not throw an error like in 1.8! + def self.respond_to_missing?(m, include_private = false) + has_view?(m) || has_view?(m.to_s[/^find_(by_.+)/, 1]) + end + def to_key new? ? nil : [id] end diff --git a/spec/unit/view_spec.rb b/spec/unit/view_spec.rb index b58e037..d69630c 100644 --- a/spec/unit/view_spec.rb +++ b/spec/unit/view_spec.rb @@ -173,7 +173,22 @@ describe CouchRest::Model::Views do end end - + + describe "#method_missing for find_by methods" do + before(:all) { reset_test_db! } + + specify { Course.should respond_to :find_by_title_and_active } + specify { Course.should respond_to :by_title } + + specify "#method should work in ruby 1.9, but not 1.8" do + if RUBY_VERSION >= "1.9" + Course.method(:find_by_title_and_active).should be_a Method + else + expect { Course.method(:find_by_title_and_active) }.to raise_error(NameError) + end + end + end + describe "a ducktype view" do before(:all) do reset_test_db! From 465c0681e22aa47dbcd1ec3c15575eb3c6819a08 Mon Sep 17 00:00:00 2001 From: Kim Burgestrand Date: Sun, 31 Jul 2011 04:44:35 +0200 Subject: [PATCH 4/9] Tell contextual validations specs which database to use --- spec/fixtures/models/base.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/fixtures/models/base.rb b/spec/fixtures/models/base.rb index b64df11..8e62eb8 100644 --- a/spec/fixtures/models/base.rb +++ b/spec/fixtures/models/base.rb @@ -85,11 +85,13 @@ end # Following two fixture classes have __intentionally__ diffent syntax for setting the validation context class WithContextualValidationOnCreate < CouchRest::Model::Base + use_database TEST_SERVER.default_database property(:name, String) validates(:name, :presence => {:on => :create}) end class WithContextualValidationOnUpdate < CouchRest::Model::Base + use_database TEST_SERVER.default_database property(:name, String) validates(:name, :presence => true, :on => :update) end From 2f00599209b415d0f374b010b28674128ca2eb86 Mon Sep 17 00:00:00 2001 From: Marcos Tapajos Date: Sun, 21 Aug 2011 01:59:48 -0300 Subject: [PATCH 5/9] added rake to gemfile --- couchrest_model.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/couchrest_model.gemspec b/couchrest_model.gemspec index fe58c09..cc22676 100644 --- a/couchrest_model.gemspec +++ b/couchrest_model.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |s| s.add_development_dependency(%q, "~> 2.6.0") s.add_development_dependency(%q, ["~> 1.5.1"]) s.add_development_dependency(%q, ">= 0.5.7") + s.add_development_dependency("rake", ">= 0.8.0") # s.add_development_dependency("jruby-openssl", ">= 0.7.3") end From 1c695f58bf76e310259f5928c884c961bfa47586 Mon Sep 17 00:00:00 2001 From: Marcos Tapajos Date: Sun, 21 Aug 2011 02:02:56 -0300 Subject: [PATCH 6/9] update readme --- history.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/history.md b/history.md index 01f0c44..a8b18ce 100644 --- a/history.md +++ b/history.md @@ -1,5 +1,9 @@ # CouchRest Model Change History +## 1.1.3 + + * CouchRest::Model::Base.respond_to_missing? and respond_to? (Kim Burgestrand) + ## 1.1.2 - 2011-07-23 * Minor fixes From f2c16144b074dc8fef1186bfb8cb3b05a6af995d Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Wed, 16 Nov 2011 15:30:46 -0700 Subject: [PATCH 7/9] #view method works when auto_update_design_doc is disabled --- lib/couchrest/model/designs.rb | 7 ++++--- spec/couchrest/designs_spec.rb | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) 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/spec/couchrest/designs_spec.rb b/spec/couchrest/designs_spec.rb index 3fa2f6a..3864b69 100644 --- a/spec/couchrest/designs_spec.rb +++ b/spec/couchrest/designs_spec.rb @@ -84,7 +84,23 @@ describe "Design" 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 From 39c60d77d2f31c59c530a49ba259fab629c7f35f Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Mon, 28 Nov 2011 16:47:13 -0700 Subject: [PATCH 8/9] Used stored design document if auto_update_design_doc is disabled --- lib/couchrest/model/design_doc.rb | 6 ++- spec/couchrest/design_doc_spec.rb | 81 +++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/lib/couchrest/model/design_doc.rb b/lib/couchrest/model/design_doc.rb index b634351..d4e46ea 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 + end end def design_doc_id diff --git a/spec/couchrest/design_doc_spec.rb b/spec/couchrest/design_doc_spec.rb index efda852..5be2ddf 100644 --- a/spec/couchrest/design_doc_spec.rb +++ b/spec/couchrest/design_doc_spec.rb @@ -162,39 +162,68 @@ describe "Design Documents" 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 From 447b11a39795d9615c56b53eea5f0cbe4c736c0e Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Thu, 1 Dec 2011 09:19:15 -0700 Subject: [PATCH 9/9] Improved auto_update_design_doc handling. --- lib/couchrest/model/design_doc.rb | 29 +++++++++++++++++++---------- lib/couchrest/model/views.rb | 3 +++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/lib/couchrest/model/design_doc.rb b/lib/couchrest/model/design_doc.rb index d4e46ea..36c3940 100644 --- a/lib/couchrest/model/design_doc.rb +++ b/lib/couchrest/model/design_doc.rb @@ -10,7 +10,7 @@ module CouchRest @design_doc ||= if auto_update_design_doc ::CouchRest::Design.new(default_design_doc) else - stored_design_doc + stored_design_doc || ::CouchRest::Design.new(default_design_doc) end end @@ -79,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/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}')"