Browse files

DataMapper pagination rewritten from the ground up

  • Loading branch information...
1 parent 95a03d8 commit 4ced819420fc3123797bbb848bccac55e74e1fb2 @mislav committed Jul 28, 2011
View
1 Gemfile
@@ -12,6 +12,7 @@ gem 'rspec', '~> 2.6.0'
gem 'mocha', '~> 0.9.8'
gem 'sqlite3', '~> 1.3.3'
gem 'dm-core'
+gem 'dm-aggregates'
gem 'dm-migrations'
gem 'dm-sqlite-adapter'
gem 'mysql', '~> 2.8.1', :group => :mysql
View
3 Gemfile.lock
@@ -36,6 +36,8 @@ GEM
data_objects (0.10.6)
addressable (~> 2.1)
diff-lcs (1.1.2)
+ dm-aggregates (1.1.0)
+ dm-core (~> 1.1.0)
dm-core (1.1.0)
addressable (~> 2.2.4)
dm-do-adapter (1.1.0)
@@ -106,6 +108,7 @@ DEPENDENCIES
actionpack (~> 3.1.0.rc)
activerecord (~> 3.1.0.rc)
activeresource (~> 3.1.0.rc)
+ dm-aggregates
dm-core
dm-migrations
dm-sqlite-adapter
View
90 lib/will_paginate/data_mapper.rb
@@ -0,0 +1,90 @@
+require 'dm-core'
+require 'dm-aggregates'
+require 'will_paginate/per_page'
+require 'will_paginate/collection'
+
+module WillPaginate
+ module DataMapper
+ module Pagination
+ def page(num)
+ pagenum = num.nil? ? 1 : num.to_i
+ raise ::WillPaginate::InvalidPage, num, pagenum if pagenum < 1
+ options = {:offset => (pagenum-1) * (query.limit || per_page)}
+ options[:limit] = per_page unless query.limit
+ col = new_collection(query.merge(options))
+ col.current_page = pagenum
+ col
+ end
+
+ def paginate(options)
+ options = options.dup
+ pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" }
+ per_page = options.delete(:per_page) || self.per_page
+
+ options.delete(:page)
+ options[:limit] = per_page
+
+ all(options).page(pagenum)
+ end
+ end
+
+ module CollectionMethods
+ attr_accessor :current_page
+
+ def paginated?
+ !current_page.nil?
+ end
+
+ def per_page
+ query.limit || model.per_page
+ end
+
+ def offset
+ query.offset
+ end
+
+ def total_entries
+ @total_entries ||= begin
+ if loaded? and @array.size < per_page and (current_page == 1 or @array.size > 0)
+ offset + @array.size
+ else
+ clean_query = query.merge(:order => [])
+ # seems like the only way
+ clean_query.instance_variable_set('@limit', nil)
+ clean_query.instance_variable_set('@offset', 0)
+ new_collection(clean_query).count
+ end
+ end
+ end
+
+ def total_pages
+ (total_entries / per_page.to_f).ceil
+ end
+
+ def to_a
+ ::WillPaginate::Collection.create(current_page, per_page) do |col|
+ col.replace super
+ col.total_entries ||= total_entries
+ end
+ end
+
+ private
+
+ def new_collection(query, resources = nil)
+ col = super
+ col.current_page = self.current_page
+ col
+ end
+
+ def initialize_copy(original)
+ super
+ @total_entries = nil
+ end
+ end
+
+ ::DataMapper::Model.append_extensions PerPage
+ ::DataMapper::Model.append_extensions Pagination
+ ::DataMapper::Collection.send(:include, Pagination)
+ ::DataMapper::Collection.send(:include, CollectionMethods)
+ end
+end
View
30 lib/will_paginate/finders/data_mapper.rb
@@ -1,30 +0,0 @@
-require 'will_paginate/finders/base'
-require 'dm-core'
-
-module WillPaginate::Finders
- module DataMapper
- include WillPaginate::Finders::Base
-
- protected
-
- def wp_query(options, pager, args, &block) #:nodoc
- find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page)
-
- pager.replace all(find_options, &block)
-
- unless pager.total_entries
- pager.total_entries = wp_count(options)
- end
- end
-
- def wp_count(options) #:nodoc
- count_options = options.except(:count, :order)
- # merge the hash found in :count
- count_options.update options[:count] if options[:count]
-
- count_options.empty?? count() : count(count_options)
- end
- end
-end
-
-DataMapper::Model.send(:include, WillPaginate::Finders::DataMapper)
View
8 spec/console
@@ -1,4 +1,10 @@
#!/usr/bin/env ruby
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
-opts = %w[--simple-prompt -rirb/completion -rconsole_fixtures]
+opts = %w[ --simple-prompt -rirb/completion ]
+if ARGV.include? '-dm'
+ opts << '-rwill_paginate/data_mapper' << '-rfinders/data_mapper_test_connector'
+else
+ opts << '-rconsole_fixtures'
+end
+
exec 'bundle', 'exec', irb, '-Ilib:spec', *opts
View
107 spec/finders/data_mapper_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
begin
- require 'will_paginate/finders/data_mapper'
+ require 'will_paginate/data_mapper'
require File.expand_path('../data_mapper_test_connector', __FILE__)
rescue LoadError => error
warn "Error running DataMapper specs: #{error.message}"
@@ -10,59 +10,70 @@
datamapper_loaded = true
end
-describe WillPaginate::Finders::DataMapper do
-
- it "should make #paginate available to DM resource classes" do
- Animal.should respond_to(:paginate)
+describe WillPaginate::DataMapper do
+
+ it "has per_page" do
+ Animal.per_page.should == 30
+ begin
+ Animal.per_page = 10
+ Animal.per_page.should == 10
+
+ subclass = Class.new(Animal)
+ subclass.per_page.should == 10
+ ensure
+ Animal.per_page = 30
+ end
end
-
- it "should paginate" do
- Animal.expects(:all).with(:limit => 5, :offset => 0).returns([])
- Animal.paginate(:page => 1, :per_page => 5)
+
+ it "doesn't make normal collections appear paginated" do
+ Animal.all.should_not be_paginated
end
-
- it "should NOT to paginate_by_sql" do
- Animal.should_not respond_to(:paginate_by_sql)
+
+ it "paginates to first page by default" do
+ animals = Animal.paginate(:page => nil)
+
+ animals.should be_paginated
+ animals.current_page.should == 1
+ animals.per_page.should == 30
+ animals.offset.should == 0
+ animals.total_entries.should == 3
+ animals.total_pages.should == 1
end
-
- it "should support explicit :all argument" do
- Animal.expects(:all).with(instance_of(Hash)).returns([])
- Animal.paginate(:all, :page => nil)
+
+ it "paginates to first page, explicit limit" do
+ animals = Animal.paginate(:page => 1, :per_page => 2)
+
+ animals.current_page.should == 1
+ animals.per_page.should == 2
+ animals.total_entries.should == 3
+ animals.total_pages.should == 2
+ animals.map {|a| a.name }.should == %w[ Dog Cat ]
end
-
- it "should support conditional pagination" do
- filtered_result = Animal.paginate(:all, :name => 'Dog', :page => nil)
- filtered_result.size.should == 1
- filtered_result.first.should == Animal.first(:name => 'Dog')
+
+ it "paginates to second page" do
+ animals = Animal.paginate(:page => 2, :per_page => 2)
+
+ animals.current_page.should == 2
+ animals.offset.should == 2
+ animals.map {|a| a.name }.should == %w[ Lion ]
end
-
- it "should leave extra parameters intact" do
- Animal.expects(:all).with(:name => 'Dog', :limit => 4, :offset => 0 ).returns(Array.new(5))
- Animal.expects(:count).with({:name => 'Dog'}).returns(1)
-
- Animal.paginate :name => 'Dog', :page => 1, :per_page => 4
+
+ it "paginates a collection" do
+ friends = Animal.all(:notes.like => '%friend%')
+ friends.paginate(:page => 1).per_page.should == 30
+ friends.paginate(:page => 1, :per_page => 1).total_entries.should == 2
end
- describe "counting" do
- it "should ignore nil in :count parameter" do
- lambda { Animal.paginate :page => nil, :count => nil }.should_not raise_error
- end
-
- it "should guess the total count" do
- Animal.expects(:all).returns(Array.new(2))
- Animal.expects(:count).never
-
- result = Animal.paginate :page => 2, :per_page => 4
- result.total_entries.should == 6
- end
-
- it "should guess that there are no records" do
- Animal.expects(:all).returns([])
- Animal.expects(:count).never
-
- result = Animal.paginate :page => 1, :per_page => 4
- result.total_entries.should == 0
- end
+ it "paginates a limited collection" do
+ animals = Animal.all(:limit => 2).paginate(:page => 1)
+ animals.per_page.should == 2
+ end
+
+ it "has page() method" do
+ Animal.page(2).per_page.should == 30
+ Animal.page(2).offset.should == 30
+ Animal.page(2).current_page.should == 2
+ Animal.all(:limit => 2).page(2).per_page.should == 2
end
-
+
end if datamapper_loaded
View
8 spec/finders/data_mapper_test_connector.rb
@@ -1,4 +1,5 @@
require 'dm-core'
+require 'dm-core/support/logger'
require 'dm-migrations'
DataMapper.setup :default, 'sqlite3::memory:'
@@ -19,4 +20,9 @@ def self.setup
# Load fixtures
Animal.auto_migrate!
-Animal.setup
+Animal.setup
+
+if 'irb' == $0
+ DataMapper.logger.set_log($stdout, :debug)
+ DataMapper.logger.auto_flush = true
+end

0 comments on commit 4ced819

Please sign in to comment.