From 67235649693f43fb845a34f57a7370747469f314 Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Sun, 27 Feb 2011 19:06:37 +0100 Subject: [PATCH] Adding initial pagination support based on kaminari --- lib/couchrest/model/designs.rb | 19 +++++++++++ lib/couchrest/model/designs/view.rb | 45 ++++++++++++++++++++++-- spec/couchrest/designs/view_spec.rb | 47 +++++++++++++++++++++++++ spec/couchrest/designs_spec.rb | 53 ++++++++++++++++++----------- 4 files changed, 143 insertions(+), 21 deletions(-) diff --git a/lib/couchrest/model/designs.rb b/lib/couchrest/model/designs.rb index 577eea5..e0d1850 100644 --- a/lib/couchrest/model/designs.rb +++ b/lib/couchrest/model/designs.rb @@ -21,6 +21,8 @@ module CouchRest module ClassMethods + # Add views and other design document features + # to the current model. def design(*args, &block) mapper = DesignMapper.new(self) mapper.create_view_method(:all) @@ -30,6 +32,23 @@ module CouchRest req_design_doc_refresh end + # Override the default page pagination value: + # + # class Person < CouchRest::Model::Base + # paginates_per 10 + # end + # + def paginates_per(val) + @_default_per_page = val + end + + # The models number of documents to return + # by default when performing pagination. + # Returns 25 unless explicitly overridden via paginates_per + def default_per_page + @_default_per_page || 25 + end + end # diff --git a/lib/couchrest/model/designs/view.rb b/lib/couchrest/model/designs/view.rb index 7708f07..1850a9a 100644 --- a/lib/couchrest/model/designs/view.rb +++ b/lib/couchrest/model/designs/view.rb @@ -14,7 +14,7 @@ module CouchRest class View include Enumerable - attr_accessor :model, :name, :query, :result + attr_accessor :owner, :model, :name, :query, :result # Initialize a new View object. This method should not be called from # outside CouchRest Model. @@ -22,11 +22,13 @@ module CouchRest if parent.is_a?(Class) && parent < CouchRest::Model::Base raise "Name must be provided for view to be initialized" if name.nil? self.model = parent + self.owner = parent self.name = name.to_s # Default options: self.query = { :reduce => false } elsif parent.is_a?(self.class) self.model = (new_query.delete(:proxy) || parent.model) + self.owner = parent.owner self.name = parent.name self.query = parent.query.dup else @@ -174,7 +176,6 @@ module CouchRest # modified appropriatly. Errors will be raised if the methods # are combined in an incorrect fashion. # - # Find all entries in the index whose key matches the value provided. # @@ -300,6 +301,46 @@ module CouchRest @docs = nil end + # == Kaminari compatible pagination support + # + # Based on the really simple support for scoped pagination in the + # the Kaminari gem, we provide compatible methods here to perform + # 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 + + def per(num) + raise "View#page must be called before #per!" if limit_value.nil? || offset_value.nil? + if (n = num.to_i) <= 0 + self + else + limit(num).skip(offset_value / limit_value * n) + end + end + + def offset_value + query[:skip] + end + + def limit_value + query[:limit] + end + + def num_pages + (total_count.to_f / limit_value).ceil + end + + def current_page + (offset_value / limit_value) + 1 + end + + + protected def include_docs! diff --git a/spec/couchrest/designs/view_spec.rb b/spec/couchrest/designs/view_spec.rb index df1f063..0164bab 100644 --- a/spec/couchrest/designs/view_spec.rb +++ b/spec/couchrest/designs/view_spec.rb @@ -541,6 +541,53 @@ describe "Design View" do 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) + @obj.should_receive(:skip).with(25).and_return(@obj) + @obj.page(2) + end + end + + describe "#per" do + it "should raise an error if page not called before hand" do + lambda { @obj.per(12) }.should raise_error + end + it "should not do anything if number less than or eql 0" do + view = @obj.page(1) + view.per(0).should eql(view) + end + it "should set limit and update skip" do + view = @obj.page(2).per(10) + view.query[:skip].should eql(10) + view.query[:limit].should eql(10) + end + end + + describe "#num_pages" do + it "should use total_count and limit_value" do + @obj.should_receive(:total_count).and_return(200) + @obj.should_receive(:limit_value).and_return(25) + @obj.num_pages.should eql(8) + end + end + + describe "#current_page" do + it "should use offset and limit" do + @obj.should_receive(:offset_value).and_return(25) + @obj.should_receive(:limit_value).and_return(25) + @obj.current_page.should eql(2) + end + end + end end end diff --git a/spec/couchrest/designs_spec.rb b/spec/couchrest/designs_spec.rb index 8897766..a035ad7 100644 --- a/spec/couchrest/designs_spec.rb +++ b/spec/couchrest/designs_spec.rb @@ -10,31 +10,46 @@ describe "Design" do DesignModel.respond_to?(:design).should be_true end - describe ".design" do + describe "class methods" do + + describe ".design" do + before :each do + @mapper = mock('DesignMapper') + @mapper.stub!(:create_view_method) + end + + it "should instantiate a new DesignMapper" do + CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(@mapper) + @mapper.should_receive(:create_view_method).with(:all) + @mapper.should_receive(:instance_eval) + DesignModel.design() { } + end + + it "should allow methods to be called in mapper" do + @mapper.should_receive(:foo) + CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper) + DesignModel.design { foo } + end + + it "should request a design refresh" do + DesignModel.should_receive(:req_design_doc_refresh) + DesignModel.design() { } + end - before :each do - @mapper = mock('DesignMapper') - @mapper.stub!(:create_view_method) end - it "should instantiate a new DesignMapper" do - CouchRest::Model::Designs::DesignMapper.should_receive(:new).with(DesignModel).and_return(@mapper) - @mapper.should_receive(:create_view_method).with(:all) - @mapper.should_receive(:instance_eval) - DesignModel.design() { } + describe "default_per_page" do + it "should return 25 default" do + DesignModel.default_per_page.should eql(25) + end end - it "should allow methods to be called in mapper" do - @mapper.should_receive(:foo) - CouchRest::Model::Designs::DesignMapper.stub!(:new).and_return(@mapper) - DesignModel.design { foo } + describe ".paginates_per" do + it "should set the default per page value" do + DesignModel.paginates_per(21) + DesignModel.default_per_page.should eql(21) + end end - - it "should request a design refresh" do - DesignModel.should_receive(:req_design_doc_refresh) - DesignModel.design() { } - end - end describe "DesignMapper" do