From 0a35be71677ef44b24abee27e2a8ccac6f1df79b Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Sun, 27 Feb 2011 20:18:19 +0100 Subject: [PATCH] Docs for pagination, not including docs in reduce and raising errors when cannot include docs --- README.md | 15 +++++++ lib/couchrest/model/designs/view.rb | 13 +++--- spec/couchrest/designs/view_spec.rb | 66 ++++++++++++++++++++++++++--- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bf4620b..4e3729e 100644 --- a/README.md +++ b/README.md @@ -364,6 +364,21 @@ You'll see that this new syntax requires all views to be defined inside a design puts "Tag: #{row.key} Uses: #{row.value}" end +#### Pagination + +The view objects have built in support for pagination based on the [kaminari](https://github.com/amatsuda/kaminari) gem. Methods are provided to match those required by the library to peform pagination. + +For your view to support paginating the results, it must use a reduce function that provides a total count of the documents in the result set. By default, auto-generated views include a reduce function that supports this. + +Use pagination as follows: + + # Prepare a query: + @posts = Post.by_title.page(params[:page]).per(10) + + # In your view, with the kaminari gem loaded: + paginate @posts + + ## Assocations Two types at the moment: diff --git a/lib/couchrest/model/designs/view.rb b/lib/couchrest/model/designs/view.rb index 1850a9a..a1f4055 100644 --- a/lib/couchrest/model/designs/view.rb +++ b/lib/couchrest/model/designs/view.rb @@ -249,7 +249,7 @@ module CouchRest # will fail. def reduce raise "Cannot reduce a view without a reduce method" unless can_reduce? - update_query(:reduce => true) + update_query(:reduce => true, :include_docs => nil) end # Control whether the reduce function reduces to a set of distinct keys @@ -308,8 +308,6 @@ module CouchRest # the same actions you'd expect. # - alias :total_count :count - def page(page) limit(owner.default_per_page).skip(owner.default_per_page * ([page.to_i, 1].max - 1)) end @@ -323,6 +321,10 @@ module CouchRest end end + def total_count + @total_count ||= limit(nil).skip(nil).count + end + def offset_value query[:skip] end @@ -344,6 +346,7 @@ module CouchRest protected def include_docs! + raise "Cannot include documents in view that has been reduced!" if query[:reduce] reset! if result && !include_docs? query[:include_docs] = true self @@ -376,7 +379,7 @@ module CouchRest # Remove the reduce value if its not needed query.delete(:reduce) unless can_reduce? begin - self.result = model.design_doc.view_on(use_database, name, query) + self.result = model.design_doc.view_on(use_database, name, query.reject{|k,v| v.nil?}) rescue RestClient::ResourceNotFound => e if retryable model.save_design_doc(use_database) @@ -474,7 +477,7 @@ module CouchRest def doc return model.build_from_database(self['doc']) if self['doc'] doc_id = (value.is_a?(Hash) && value['_id']) ? value['_id'] : self.id - model.get(doc_id) + doc_id ? model.get(doc_id) : nil end end diff --git a/spec/couchrest/designs/view_spec.rb b/spec/couchrest/designs/view_spec.rb index 0164bab..7aa032e 100644 --- a/spec/couchrest/designs/view_spec.rb +++ b/spec/couchrest/designs/view_spec.rb @@ -367,7 +367,7 @@ describe "Design View" do describe "#reduce" do it "should update query" do @obj.should_receive(:can_reduce?).and_return(true) - @obj.should_receive(:update_query).with({:reduce => true}) + @obj.should_receive(:update_query).with({:reduce => true, :include_docs => nil}) @obj.reduce end it "should raise error if query cannot be reduced" do @@ -442,6 +442,10 @@ describe "Design View" do @obj.send(:include_docs!) @obj.query[:include_docs].should be_true end + it "should raise an error if view is reduced" do + @obj.query[:reduce] = true + lambda { @obj.send(:include_docs!) }.should raise_error + end end describe "#include_docs?" do @@ -538,17 +542,19 @@ describe "Design View" do lambda { @obj.send(:execute) }.should raise_error(RestClient::ResourceNotFound) end + it "should remove nil values from query" do + @obj.should_receive(:can_reduce?).and_return(true) + @obj.stub!(:use_database).and_return('database') + @obj.query = {:reduce => true, :limit => nil, :skip => nil} + @design_doc.should_receive(:view_on).with('database', 'test_view', {:reduce => true}) + @obj.send(:execute) + end + end describe "pagination methods" do - describe "#total_count" do - it "should be an alias for count" do - @obj.method(:total_count).should eql(@obj.method(:count)) - end - end - describe "#page" do it "should call limit and skip" do @obj.should_receive(:limit).with(25).and_return(@obj) @@ -572,6 +578,16 @@ describe "Design View" do end end + describe "#total_count" do + it "set limit and skip to nill and perform count" do + @obj.should_receive(:limit).with(nil).and_return(@obj) + @obj.should_receive(:skip).with(nil).and_return(@obj) + @obj.should_receive(:count).and_return(5) + @obj.total_count.should eql(5) + @obj.total_count.should eql(5) # Second to test caching + end + end + describe "#num_pages" do it "should use total_count and limit_value" do @obj.should_receive(:total_count).and_return(200) @@ -657,6 +673,15 @@ describe "Design View" do obj.doc.should eql(doc) end + it "should try to return nil for document if none available" do + hash = {'value' => 23} # simulate reduce + obj = @klass.new(hash, DesignViewModel) + doc = mock('DesignViewModel') + obj.model.should_not_receive(:get) + obj.doc.should be_nil + end + + end end @@ -708,6 +733,33 @@ describe "Design View" do end end + describe "pagination" do + before :all do + DesignViewModel.paginates_per 3 + end + before :each do + @view = DesignViewModel.by_name.page(1) + end + + it "should calculate number of pages" do + @view.num_pages.should eql(2) + end + it "should return results from first page" do + @view.all.first.name.should eql('Judith') + @view.all.last.name.should eql('Peter') + end + it "should return results from second page" do + @view.page(2).all.first.name.should eql('Sam') + @view.page(2).all.last.name.should eql('Vilma') + end + + it "should allow overriding per page count" do + @view = @view.per(10) + @view.num_pages.should eql(1) + @view.all.last.name.should eql('Vilma') + end + end + end