diff --git a/Gemfile.lock b/Gemfile.lock index 25cd3a7..67e26b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ PATH specs: couchrest_model (1.1.0.beta2) activemodel (~> 3.0.0) - couchrest (~> 1.1.0.pre) + couchrest (= 1.1.0.pre2) mime-types (~> 1.15) railties (~> 3.0.0) tzinfo (~> 0.3.22) @@ -28,7 +28,7 @@ GEM i18n (~> 0.4) activesupport (3.0.5) builder (2.1.2) - couchrest (1.1.0.pre) + couchrest (1.1.0.pre2) json (~> 1.5.1) mime-types (~> 1.15) rest-client (~> 1.6.1) @@ -67,7 +67,7 @@ PLATFORMS DEPENDENCIES activemodel (~> 3.0.0) - couchrest (~> 1.1.0.pre) + couchrest (= 1.1.0.pre2) couchrest_model! mime-types (~> 1.15) rack-test (>= 0.5.7) diff --git a/couchrest_model.gemspec b/couchrest_model.gemspec index 4290bf1..d77f44a 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.0.pre") + s.add_dependency(%q, "1.1.0.pre2") s.add_dependency(%q, "~> 1.15") s.add_dependency(%q, "~> 3.0.0") s.add_dependency(%q, "~> 0.3.22") diff --git a/history.txt b/history.txt index e2d63ff..986cf0b 100644 --- a/history.txt +++ b/history.txt @@ -5,6 +5,7 @@ * Refinements to associations and uniqueness validation for proxy (based on issue found by Gleb Kanterov) * Added :allow_nil and :allow_blank options when creating a new view * Unique Validation now supports scopes! + * Added support for #keys with list on Design View. == 1.1.0.beta diff --git a/lib/couchrest/model/core_extensions/time_parsing.rb b/lib/couchrest/model/core_extensions/time_parsing.rb index 32e6087..f8f09b8 100644 --- a/lib/couchrest/model/core_extensions/time_parsing.rb +++ b/lib/couchrest/model/core_extensions/time_parsing.rb @@ -3,6 +3,29 @@ module CouchRest module CoreExtensions module TimeParsing + if RUBY_VERSION < "1.9.0" + # Overrwrite Ruby's standard new method to provide compatible support + # of 1.9.2's Time.new method. + # + # Only supports syntax like: + # + # Time.new(2011, 4, 1, 18, 50, 32, "+02:00") + # # or + # Time.new(2011, 4, 1, 18, 50, 32) + # + def new(*args) + return super() if (args.empty?) + zone = args.delete_at(6) + time = mktime(*args) + if zone =~ /([\+|\-]?)(\d{2}):?(\d{2})/ + tz_difference = ("#{$1 == '-' ? '+' : '-'}#{$2}".to_i * 3600) + ($3.to_i * 60) + time + tz_difference + zone_offset(time.zone) + else + time + end + end + end + # Attemtps to parse a time string in ISO8601 format. # If no match is found, the standard time parse will be used. # diff --git a/lib/couchrest/model/designs/view.rb b/lib/couchrest/model/designs/view.rb index 766da3c..11c987a 100644 --- a/lib/couchrest/model/designs/view.rb +++ b/lib/couchrest/model/designs/view.rb @@ -25,7 +25,7 @@ module CouchRest self.owner = parent self.name = name.to_s # Default options: - self.query = { :reduce => false } + self.query = { } elsif parent.is_a?(self.class) self.model = (new_query.delete(:proxy) || parent.model) self.owner = parent.owner @@ -139,12 +139,6 @@ module CouchRest execute['total_rows'] end - # Convenience wrapper around the rows result set. This will provide - # and array of keys. - def keys - rows.map{|r| r.key} - end - # Convenience wrapper to provide all the values from the route # set without having to go through +rows+. def values @@ -181,7 +175,7 @@ module CouchRest # # Cannot be used when the +#startkey+ or +#endkey+ have been set. def key(value) - raise "View#key cannot be used when startkey or endkey have been set" unless query[:startkey].nil? && query[:endkey].nil? + raise "View#key cannot be used when startkey or endkey have been set" unless query[:keys].nil? && query[:startkey].nil? && query[:endkey].nil? update_query(:key => value) end @@ -193,7 +187,7 @@ module CouchRest # # Cannot be used if the key has been set. def startkey(value) - raise "View#startkey cannot be used when key has been set" unless query[:key].nil? + raise "View#startkey cannot be used when key has been set" unless query[:key].nil? && query[:keys].nil? update_query(:startkey => value) end @@ -210,7 +204,7 @@ module CouchRest # See the +#startkey+ method for more details and the +#inclusive_end+ # option. def endkey(value) - raise "View#endkey cannot be used when key has been set" unless query[:key].nil? + raise "View#endkey cannot be used when key has been set" unless query[:key].nil? && query[:keys].nil? update_query(:endkey => value) end @@ -221,6 +215,22 @@ module CouchRest update_query(:endkey_docid => value.is_a?(String) ? value : value.id) end + # Keys is a special CouchDB option that will cause the view request to be POSTed + # including an array of keys. Only documents with the matching keys will be + # returned. This is much faster than sending multiple requests for a set + # non-consecutive documents. + # + # If no values are provided, this method will act as a wrapper around + # the rows result set, providing an array of keys. + def keys(*keys) + if keys.empty? + rows.map{|r| r.key} + else + raise "View#keys cannot by used when key or startkey/endkey have been set" unless query[:key].nil? && query[:startkey].nil? && query[:endkey].nil? + update_query(:keys => keys.first) + end + end + # The results should be provided in descending order. # @@ -341,8 +351,6 @@ module CouchRest (offset_value / limit_value) + 1 end - - protected def include_docs! @@ -371,7 +379,7 @@ module CouchRest def use_database query[:database] || model.database end - + def execute return self.result if result raise "Database must be defined in model or view!" if use_database.nil? diff --git a/lib/couchrest/model/validations/uniqueness.rb b/lib/couchrest/model/validations/uniqueness.rb index d76ed09..c289849 100644 --- a/lib/couchrest/model/validations/uniqueness.rb +++ b/lib/couchrest/model/validations/uniqueness.rb @@ -29,7 +29,8 @@ module CouchRest if base.respond_to?(:has_view?) && !base.has_view?(view_name) raise "View #{document.class.name}.#{options[:view]} does not exist!" unless options[:view].nil? - model.view_by *keys, :allow_nil => true + keys << {:allow_nil => true} + model.view_by(*keys) end rows = base.view(view_name, :key => values, :limit => 2, :include_docs => false)['rows'] diff --git a/spec/couchrest/designs/view_spec.rb b/spec/couchrest/designs/view_spec.rb index 7aa032e..36d8923 100644 --- a/spec/couchrest/designs/view_spec.rb +++ b/spec/couchrest/designs/view_spec.rb @@ -7,6 +7,7 @@ class DesignViewModel < CouchRest::Model::Base design do view :by_name + view :by_just_name, :map => "function(doc) { emit(doc['name'], null); }" end end @@ -32,7 +33,7 @@ describe "Design View" do @obj = @klass.new(DesignViewModel, {}, 'test_view') @obj.model.should eql(DesignViewModel) @obj.name.should eql('test_view') - @obj.query.should eql({:reduce => false}) + @obj.query.should be_empty end it "should complain if there is no name" do @@ -51,7 +52,7 @@ describe "Design View" do it "should copy attributes" do @obj.model.should eql(DesignViewModel) @obj.name.should eql('test_view') - @obj.query.should eql({:reduce => false, :foo => :bar}) + @obj.query.should eql({:foo => :bar}) end end @@ -233,15 +234,6 @@ describe "Design View" do end end - describe "#keys" do - it "should request each row and provide key value" do - row = mock("Row") - row.should_receive(:key).twice.and_return('foo') - @obj.should_receive(:rows).and_return([row, row]) - @obj.keys.should eql(['foo', 'foo']) - end - end - describe "#values" do it "should request each row and provide value" do row = mock("Row") @@ -276,21 +268,25 @@ describe "Design View" do @obj.should_receive(:update_query).with({:key => 'foo'}) @obj.key('foo') end - it "should raise and error if startkey set" do + it "should raise error if startkey set" do @obj.query[:startkey] = 'bar' lambda { @obj.key('foo') }.should raise_error end - it "should raise and error if endkey set" do + it "should raise error if endkey set" do @obj.query[:endkey] = 'bar' lambda { @obj.key('foo') }.should raise_error end - it "should raise and error if both startkey and endkey set" do + it "should raise error if both startkey and endkey set" do @obj.query[:startkey] = 'bar' @obj.query[:endkey] = 'bar' lambda { @obj.key('foo') }.should raise_error end + it "should raise error if keys set" do + @obj.query[:keys] = 'bar' + lambda { @obj.key('foo') }.should raise_error + end end - + describe "#startkey" do it "should update query with value" do @obj.should_receive(:update_query).with({:startkey => 'foo'}) @@ -298,7 +294,11 @@ describe "Design View" do end it "should raise and error if key set" do @obj.query[:key] = 'bar' - lambda { @obj.startkey('foo') }.should raise_error + lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/) + end + it "should raise and error if keys set" do + @obj.query[:keys] = 'bar' + lambda { @obj.startkey('foo') }.should raise_error(/View#startkey/) end end @@ -322,7 +322,11 @@ describe "Design View" do end it "should raise and error if key set" do @obj.query[:key] = 'bar' - lambda { @obj.endkey('foo') }.should raise_error + lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/) + end + it "should raise and error if keys set" do + @obj.query[:keys] = 'bar' + lambda { @obj.endkey('foo') }.should raise_error(/View#endkey/) end end @@ -339,6 +343,33 @@ describe "Design View" do end end + describe "#keys" do + it "should update the query" do + @obj.should_receive(:update_query).with({:keys => ['foo', 'bar']}) + @obj.keys(['foo', 'bar']) + end + it "should raise and error if key set" do + @obj.query[:key] = 'bar' + lambda { @obj.keys('foo') }.should raise_error(/View#keys/) + end + it "should raise and error if startkey or endkey set" do + @obj.query[:startkey] = 'bar' + lambda { @obj.keys('foo') }.should raise_error(/View#keys/) + @obj.query.delete(:startkey) + @obj.query[:endkey] = 'bar' + lambda { @obj.keys('foo') }.should raise_error(/View#keys/) + end + end + + describe "#keys (without parameters)" do + it "should request each row and provide key value" do + row = mock("Row") + row.should_receive(:key).twice.and_return('foo') + @obj.should_receive(:rows).and_return([row, row]) + @obj.keys.should eql(['foo', 'foo']) + end + end + describe "#descending" do it "should update query" do @obj.should_receive(:update_query).with({:descending => true}) @@ -700,7 +731,6 @@ describe "Design View" do end describe "loading documents" do - it "should return first" do DesignViewModel.by_name.first.name.should eql("Judith") end @@ -715,7 +745,6 @@ describe "Design View" do view.last.name.should eql("Peter") view.all.length.should eql(3) end - end describe "index information" do @@ -733,6 +762,18 @@ describe "Design View" do end end + describe "viewing" do + it "should load views with no reduce method" do + docs = DesignViewModel.by_just_name.all + docs.length.should eql(5) + end + it "should load documents by specific keys" do + docs = DesignViewModel.by_name.keys(["Judith", "Peter"]).all + docs[0].name.should eql("Judith") + docs[1].name.should eql("Peter") + end + end + describe "pagination" do before :all do DesignViewModel.paginates_per 3 diff --git a/spec/couchrest/typecast_spec.rb b/spec/couchrest/typecast_spec.rb index 913764f..4174001 100644 --- a/spec/couchrest/typecast_spec.rb +++ b/spec/couchrest/typecast_spec.rb @@ -447,12 +447,15 @@ describe "Type Casting" do t = Time.new(2011, 4, 1, 18, 50, 32, "+02:00") @course.ends_at = t @course.ends_at.utc?.should be_true - @course.ends_at.should eql(Time.utc(2011, 4, 1, 16, 50, 32)) + @course.ends_at.to_i.should eql(Time.utc(2011, 4, 1, 16, 50, 32).to_i) end - it 'does not typecast non-time values' do - @course.ends_at = 'not-time' - @course['ends_at'].should eql('not-time') + if RUBY_VERSION >= "1.9.1" + # In ruby 1.8.7 Time.parse will always return a value. D'OH + it 'does not typecast non-time values' do + @course.ends_at = 'not-time' + @course['ends_at'].should eql('not-time') + end end end