jm81-paginate 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jared Morgan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ Paginate
2
+ ========
3
+
4
+ This paginate library assists in paginating collections and results of database
5
+ queries. It is particularly designed for use with DataMapper and ActiveRecord,
6
+ and for the Merb and Rails frameworks, but can be used in many other situations.
7
+ The library includes three sections (with more information on each below)
8
+
9
+ 1. Paginate modules - These are the modules that a collection (such as an Array)
10
+ or a model Class extends to allow the collection to call #paginate
11
+
12
+ 2. Paginator classes - classes in the Paginator module do the actual work.
13
+ Paginator::Simple is the base class.
14
+
15
+ 3. Helper modules - modules with helper methods for use by frameworks
16
+ (currently Merb only).
17
+
18
+ ##General Information
19
+
20
+ There are many pagination libraries available for Ruby (in particular for Ruby
21
+ ORMs). This is just one more, but with three particular design goals:
22
+
23
+ 1. Add *#current_page* and *pages* singleton methods to the paginated
24
+ collection. The pagination libraries I've used usually return two values:
25
+ the collection and a total pages value. For no good reason, this bugs me.
26
+ 2. A more flexible *:page* option. The *:page* option can be negative,
27
+ representing number of pages from the last (-1 being the last), as occurs
28
+ with many Ruby objects. Also, the *paginate* method will always return a
29
+ page within the actual collection. Finally, the *current_page* singleton
30
+ method returns the actual number of the page returned, which may be different
31
+ from that given.
32
+ 3. Re-use as much code as possible for different situations. That is, the method
33
+ that calculates the current page and adds the singleton methods is shared
34
+ by implementations for Array, DataMapper, ActiveRecord, etc.
35
+
36
+ ##Examples
37
+
38
+ Two examples are below, one using Paginate::Simple, one using Paginate::DM.
39
+ See below and the documentation of the individual modules for more details.
40
+
41
+ ary = Array.new(1,2,3,4,5)
42
+ ary.extend(Paginate::Simple)
43
+ paged = ary.paginate(:page => 1, :limit => 2)
44
+ => [1, 2]
45
+ paged.current_page
46
+ => 1
47
+ paged.pages
48
+ => 3
49
+
50
+ class DmModel
51
+ include DataMapper::Resource
52
+ extend Paginate::DM
53
+ end
54
+ paged = DmModel.paginate(:page => 2, :limit => 10)
55
+
56
+ Note that neither the :page nor the :limit option is required. :page defaults
57
+ to 1, :limit defaults to *Paginate.config[:default_limit]*
58
+ (See **Configuration**).
59
+
60
+ ##Configuration
61
+
62
+ Paginate has one configurable parameter (for defaults across an application):
63
+ *default_limit*. This specifies the limit (number of records per page) to use
64
+ if not given as an option to the *#paginate* method. By default, it is 10. To
65
+ change:
66
+
67
+ Paginate.config[:default_limit] = 100
68
+
69
+ For Merb apps, Merb plugin configuration can also be used:
70
+
71
+ Merb::Plugins.config[:paginate][:default_limit] = 100
72
+
73
+ ##Paginate Modules
74
+
75
+ Paginate modules provide a *paginate* method to Objects that extend them. The
76
+ actual work is done by a Paginator class.
77
+
78
+ *paginate* accepts an options hash with at least two options:
79
+
80
+ - *:page*: The desired page number, 1-indexed. Negative numbers represent
81
+ page from the last page (with -1) being the last.
82
+ - *:limit*: represents records per page.
83
+
84
+ Depending on the module, other options may be passed through to, for example, an
85
+ *all* method. See individual modules for more details.
86
+
87
+ There are currently three modules.
88
+
89
+ - Paginate::Simple - for use with Arrays and similar objects.
90
+ - Paginate::DM - extended by a class that includes DataMapper::Resource
91
+ - Paginate::AR - extended by a class that inherits ActiveRecord::Base
92
+
93
+ ##Paginator Classes
94
+
95
+ Paginator classes do the actual work of paginating and are called by the
96
+ modules. They can be used directly. The *#paginate* method in Paginators::Simple
97
+ does all the generic work. It calls two methods, *#get_full_count*,
98
+ and *#get_paginated_collection* to do the implementation-specific work.
99
+ Paginator classes should need to override only these two methods.
100
+
101
+ ##Custom Paginators
102
+
103
+ To create an additional Paginator, create a class that inherits
104
+ Paginate::Paginators::Simple (or another Paginator). The key is to override
105
+ the following methods:
106
+
107
+ - *#get_paginated_collection* - This returns the paginated collection. At it's
108
+ disposal is the @options hash and two particular options:
109
+ - @options[:offset] is the zero-indexed offset of the first record of the
110
+ page.
111
+ - @options[:limit] is the number of items per page.
112
+ - Other options passed the #initialize method will also be available.
113
+
114
+ - *#get_full_count* - Return the total number of items on all pages. The @options
115
+ hash is available here, but :offset, :order and :limit options will not be
116
+ included. Options such as conditions will be available.
117
+
118
+ See Paginators for examples.
119
+
120
+ To use, add a paginate method to your Class or Object:
121
+
122
+ def paginate(options = {})
123
+ MyPaginator.new(self, options).paginate
124
+ end
125
+
126
+ ##Helper Modules
127
+
128
+ There is currently a Helpers::Shared module and Helpers::Merb.
129
+
130
+ ###Helpers::Shared
131
+
132
+ Contains one method, *#page_set*, which returns an Array of page numbers useful
133
+ in creating page links for the collection.
134
+
135
+ ###Helpers::Merb
136
+
137
+ In addition to including Shared, this helper includes other methods useful for
138
+ displaying page links. See Helpers::Merb module for details.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ gem 'rspec'
3
+ require 'spec/rake/spectask'
4
+
5
+ QBFC_ROOT = File.dirname(__FILE__)
6
+
7
+ task :default => :spec
8
+
9
+ desc "Run all specs in spec/unit directory"
10
+ Spec::Rake::SpecTask.new(:spec) do |t|
11
+ t.spec_opts = ['--options', '"spec/spec.opts"']
12
+ t.spec_files = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ require 'rake/gempackagetask'
16
+
17
+ require 'merb-core'
18
+ require 'merb-core/tasks/merb'
19
+
20
+ GEM_NAME = "paginate"
21
+ GEM_VERSION = "0.1.1"
22
+ AUTHOR = "Jared Morgan"
23
+ EMAIL = "jmorgan@morgancreative.net"
24
+ HOMEPAGE = "http://github.com/jm81/paginate/"
25
+ SUMMARY = "(Yet another) Pagination for DataMapper, ActiveRecord, and Array"
26
+
27
+ spec = Gem::Specification.new do |s|
28
+ s.name = GEM_NAME
29
+ s.version = GEM_VERSION
30
+ s.platform = Gem::Platform::RUBY
31
+ s.has_rdoc = true
32
+ s.extra_rdoc_files = ["README.md", "LICENSE", 'TODO']
33
+ s.summary = SUMMARY
34
+ s.description = s.summary
35
+ s.author = AUTHOR
36
+ s.email = EMAIL
37
+ s.homepage = HOMEPAGE
38
+ s.require_path = 'lib'
39
+ s.files = %w(LICENSE README.md Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
40
+
41
+ end
42
+
43
+ Rake::GemPackageTask.new(spec) do |pkg|
44
+ pkg.gem_spec = spec
45
+ end
46
+
47
+ desc "install the plugin as a gem"
48
+ task :install do
49
+ Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
50
+ end
51
+
52
+ desc "Uninstall the gem"
53
+ task :uninstall do
54
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
55
+ end
56
+
57
+ desc "Create a gemspec file"
58
+ task :gemspec do
59
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
60
+ file.puts spec.to_ruby
61
+ end
62
+ end
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ TODO:
2
+ Add your code to lib/paginate.rb
@@ -0,0 +1,84 @@
1
+ module Paginate
2
+ module Helpers
3
+ # Pagination helpers for Merb applications. See Paginate::Helpers::Shared
4
+ # for additional methods
5
+ module Merb
6
+ include Shared
7
+
8
+ # A quick method for creating pagination links, using a view partial,
9
+ # (layouts/_page_links, by default).
10
+ # Arguments:
11
+ #
12
+ # - +collection+: an enumerable collection with #current_page and
13
+ # #pages methods.
14
+ # - +partial_name+: location of the partial to use
15
+ # - +padding+: Maximum number of page links before and after current_page.
16
+ def pagination_partial(collection, partial_name = "layout/page_links", padding = 3)
17
+ padding ||= 3
18
+ partial(partial_name, :current_page => collection.current_page, :pages => collection.pages, :padding => padding)
19
+ end
20
+
21
+ # Returns links for pages, given a +collection+ and optional
22
+ # +padding+ (see Shared#page_set). Returned html will be along the lines
23
+ # of:
24
+ #
25
+ # <div class="pageLinks">' +
26
+ # <span class="pagePrevious"><a href="/list?page=4">&laquo;</a></span>
27
+ # <span class="pageSpacer">...</span>
28
+ # <span class="pageNumber"><a href="/list?page=3">3</a></span>
29
+ # <span class="pageNumber"><a href="/list?page=4">4</a></span>
30
+ # <span class="pageCurrent">5</span>
31
+ # <span class="pageNumber"><a href="/list?page=6">6</a></span>
32
+ # <span class="pageNumber"><a href="/list?page=7">7</a></span>
33
+ # <span class="pageSpacer">...</span>
34
+ # <span class="pageNext"><a href="/list?page=6">&raquo;</a></span>
35
+ # </div>
36
+ #
37
+ # CSS classes are pageSpacer, pageNumber, pageDisabled, pageCurrent,
38
+ # pagePrevious, and pageNext. pageLinks is the class of the enclosing div.
39
+ def page_links(collection, padding = 3)
40
+ current = collection.current_page
41
+ pages = collection.pages
42
+
43
+ tag(:div, :class => 'pageLinks') do
44
+ if current_page == 1
45
+ tag(:span, '&laquo;', :class => 'pageDisabled pagePrevious')
46
+ else
47
+ tag(:a, '&laquo;', :href => page_url(current - 1), :class => 'pagePrevious')
48
+ end
49
+
50
+ page_set(current, pages, padding).each do |page|
51
+ case page
52
+ when 0
53
+ tag(:span, "...", :class => 'pageSpacer')
54
+ when current
55
+ tag(:span, page, :class => 'pageCurrent')
56
+ else
57
+ tag(:a, page, :href => page_url(page), :class => 'pageNumber')
58
+ end
59
+ end
60
+
61
+ if current == pages
62
+ tag(:span, '&raquo;', :class => 'pageDisabled pageNext')
63
+ else
64
+ tag(:a, '&raquo;', :href => page_url(current + 1), :class => 'pageNext')
65
+ end
66
+ end
67
+ end
68
+
69
+ # +page_url+ generates a URL (for page links), given a page number and
70
+ # optionally a path and query string. By default, the path is the path
71
+ # of the current request, and the query_string is also that of the
72
+ # current request. This allows for order and condition related fields
73
+ # in the query string to be used in the page link.
74
+ def page_url(page, path = request.path, q = request.query_string)
75
+ # Remove any current reference to page in the query string
76
+ q.to_s.gsub!(/page=(\d+)(&?)/, '')
77
+ # Assemble new link
78
+ link = "#{path}?page=#{page}&#{q}"
79
+ link = link[0..-2] if link[-1..-1] == '&' # Strip trailing ampersand
80
+ link
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,43 @@
1
+ module Paginate
2
+ module Helpers
3
+ # Shared helper methods included in other Helper modules.
4
+ module Shared
5
+ # +page_set+ returns an Array of page numbers that can be used by a view
6
+ # for displaying page links. It includes the first page, the current page,
7
+ # with up to +padding+ pages before and after, and the last page. If pages
8
+ # are skipped between any of these groups, 0 stands in for them, as an
9
+ # indicator to the view that, for example, elipses might be used here.
10
+ # For example:
11
+ #
12
+ # page_set(3, 10, 3)
13
+ # => [1, 2, 3, 4, 5, 6, 0, 10] TODO test this in particular
14
+ #
15
+ # The 0 at index 6 is an indication that there are skipped pages.
16
+ def page_set(current_page, pages, padding = 3)
17
+
18
+ # Determine first and last page in group around current page
19
+ first = [1, current_page - padding].max
20
+ last = [pages, current_page + padding].min
21
+
22
+ # Determine if an additional "First page" is needed and whether any
23
+ # pages are skipped between it and the first around the current page
24
+ leader = case first
25
+ when 1 then [] # First not needed
26
+ when 2 then [1] # First needed, but none skipped
27
+ else [1, 0] # First needed, some skipped
28
+ end
29
+
30
+ # Determine if an additional "Last page" is needed and whether any
31
+ # pages are skipped between the last around the current page and it.
32
+ footer = case last
33
+ when pages then [] # Last not needed
34
+ when pages - 1 then [pages] # Last needed, but none skipped
35
+ else [0, pages] # Last needed, some skipped
36
+ end
37
+
38
+ # Join Arrays together
39
+ leader + (first..last).to_a + footer
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/paginate.rb ADDED
@@ -0,0 +1,32 @@
1
+ module Paginate
2
+ DEFAULTS = {
3
+ :default_limit => 10
4
+ }
5
+
6
+ class << self
7
+ # Paginate::config method returns Hash that can be edited.
8
+ def config
9
+ @config ||= DEFAULTS
10
+ end
11
+ end
12
+ end
13
+
14
+ if defined?(Merb::Plugins)
15
+ # Make config accessible through Merb's Merb::Plugins.config hash
16
+ Merb::Plugins.config[:paginate] = Paginate.config
17
+ end
18
+
19
+ # Require Paginators
20
+ %w{ simple orm }.each do |file|
21
+ require File.dirname(__FILE__) + '/paginators/' + file
22
+ end
23
+
24
+ # Require Paginate Modules
25
+ %w{ simple dm ar }.each do |file|
26
+ require File.dirname(__FILE__) + '/paginate/' + file
27
+ end
28
+
29
+ # Require Helper Modules
30
+ %w{ shared merb }.each do |file|
31
+ require File.dirname(__FILE__) + '/helpers/' + file
32
+ end
@@ -0,0 +1,33 @@
1
+ module Paginate
2
+
3
+ # .pagination method for ActiveRecord models.
4
+ module AR
5
+
6
+ # Implementation of .paginate for ActiveRecord.
7
+ #
8
+ # To make available to your model:
9
+ # class MyModel < ActiveRecord::Base
10
+ # extend Paginate::AR
11
+ # ...
12
+ # end
13
+ #
14
+ # Accepts same options as ActiveRecord::Base.all, plus:
15
+ # - +page+: The desired page number, 1-indexed. Negative numbers represent
16
+ # page from the last page (with -1) being the last.
17
+ #
18
+ # The +limit+ option should also be specified and represents records per
19
+ # page. Any +offset+ option will be overriden.
20
+ #
21
+ # Returns the records for the given page number, with other options
22
+ # processed by +all+. In addition, two methods are added to the result:
23
+ # - +pages+: The total number of pages that the given options would produce.
24
+ # - +current_page+: The number of the current page (as actually returned).
25
+ # This is never a negative number even if given.
26
+ # If the +page+ option is zero, or less than (-1 * pages) or greater than
27
+ # the total pages, it will be recalculated to return either page 1 or the
28
+ # last page, respectively.
29
+ def paginate(options = {})
30
+ Paginators::ORM.new(self, options).paginate
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ module Paginate
2
+
3
+ # .pagination method for DataMapper models.
4
+ module DM
5
+
6
+ # Implementation of .paginate for DataMapper.
7
+ # (This is loosely based on http://github.com/lholden/dm-is-paginated)
8
+ #
9
+ # To make available to your model:
10
+ # class MyModel
11
+ # include DataMapper::Resource
12
+ # extend Paginate::DM
13
+ # ...
14
+ # end
15
+ #
16
+ # Accepts same options as DataMapper::Resource.all, plus:
17
+ # - +page+: The desired page number, 1-indexed. Negative numbers represent
18
+ # page from the last page (with -1) being the last.
19
+ #
20
+ # The +limit+ option should also be specified and represents records per
21
+ # page. Any +offset+ option will be overriden.
22
+ #
23
+ # Returns the records for the given page number, with other options
24
+ # processed by +all+. In addition, two methods are added to the result:
25
+ # - +pages+: The total number of pages that the given options would produce.
26
+ # - +current_page+: The number of the current page (as actually returned).
27
+ # This is never a negative number even if given.
28
+ # If the +page+ option is zero, or less than (-1 * pages) or greater than
29
+ # the total pages, it will be recalculated to return either page 1 or the
30
+ # last page, respectively.
31
+ def paginate(options = {})
32
+ Paginators::ORM.new(self, options).paginate
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ module Paginate
2
+
3
+ # .pagination method for Arrays and similar objects.
4
+ module Simple
5
+
6
+ # Implementation of .paginate for Arrays and similar objects (must respond
7
+ # to +length+ and +[](start_index, length)+.
8
+ #
9
+ # To make available to all Arrays:
10
+ # Array.include(Paginate::Simple)
11
+ #
12
+ # For a single Array:
13
+ # array.extend(Paginate::Simple)
14
+ #
15
+ # Accepts two options:
16
+ # - +page+: The desired page number, 1-indexed. Negative numbers represent
17
+ # page from the last page (with -1) being the last.
18
+ #- +limit+: represents records per page.
19
+ #
20
+ # Returns a slice of the Array with records for the given page number.
21
+ # Two methods are added to the result:
22
+ # - +pages+: The total number of pages that the given options would produce.
23
+ # - +current_page+: The number of the current page (as actually returned).
24
+ # This is never a negative number even if given.
25
+ # If the +page+ option is zero, or less than (-1 * pages) or greater than
26
+ # the total pages, it will be recalculated to return either page 1 or the
27
+ # last page, respectively.
28
+ def paginate(options = {})
29
+ Paginators::Simple.new(self, options).paginate
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ module Paginate
2
+ module Paginators
3
+ class ORM < Simple
4
+ private
5
+
6
+ # Return the total number of records based on the given conditions.
7
+ def get_full_count
8
+ model.count(:id, @options)
9
+ end
10
+
11
+ # Return the records for the given page.
12
+ def get_paginated_collection
13
+ model.all(@options)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,84 @@
1
+ module Paginate
2
+ module Paginators
3
+ # This is for use by +Array+ and similar objects.
4
+ class Simple
5
+ # full_collection is an +Array+ (or any Object that responds to +length+
6
+ # and +[](start_index, length)+, such as +LazyArray+. #paginate will
7
+ # return the appropriate slice of that Object.
8
+ # options include:
9
+ # - +page+: The desired page (may be negative)
10
+ # - +limit+: Number of items per page.
11
+ # Other options are ignored.
12
+ # See README for more details. (TODO)
13
+ def initialize(full_collection, options = {})
14
+ @full_collection = full_collection
15
+ @options = options
16
+ end
17
+
18
+ # Perform the pagination. Returns the records for the given page, with
19
+ # singleton methods:
20
+ # - +current_page+: The number of the current page.
21
+ # - +pages+: The total number of pages of records.
22
+ #
23
+ # See the documents for the module that your class extends for more details.
24
+ #
25
+ # Descendents classes should not need to override this method, but rather
26
+ # + get_full_count+ and +get_paginated_collection+ private methods.
27
+ def paginate
28
+ page = @options.delete(:page).to_i
29
+ limit = @options[:limit] ? @options[:limit].to_i : Paginate.config[:default_limit]
30
+ order = @options[:order]
31
+
32
+ # Remove some options before calling +count+ that are not applicable.
33
+ # order and limit are needed later and have been saved above.
34
+ [:offset, :limit, :order].each do |key|
35
+ @options.delete(key)
36
+ end
37
+
38
+ # Determine total number of pages and set offset option.
39
+ pages = (get_full_count.to_f / limit).ceil
40
+ page = (pages + 1 + page) if page < 0 # Negative page
41
+ page = pages if page > pages # page should not be more than total pages
42
+ page = 1 if page < 1 # Minimum is 1 even if 0 records.
43
+ pages = 1 if pages < 1 # Minimum is 1 even if 0 records.
44
+ @options[:offset] = ((page - 1) * limit)
45
+
46
+ # Add limit and order back into options, from above.
47
+ @options[:limit] = limit
48
+ @options[:order] = order if order
49
+
50
+ # Call +all+.
51
+ collection = get_paginated_collection
52
+
53
+ # Create +pages+ and +current_page+ methods for collection, for use by
54
+ # pagination links.
55
+ collection.instance_variable_set(:@pages, pages)
56
+ collection.instance_variable_set(:@current_page, page)
57
+ def collection.pages; @pages; end
58
+ def collection.current_page; @current_page; end
59
+
60
+ return collection
61
+ end
62
+
63
+ private
64
+
65
+ # Alias for @full_collection because that's what the @full_collection
66
+ # usually is for ORM class
67
+ def model
68
+ @full_collection
69
+ end
70
+
71
+ # Return the total number of items/records based on the given conditions.
72
+ # This may be overriden by inherited classes.
73
+ def get_full_count
74
+ @full_collection.length
75
+ end
76
+
77
+ # Return the records for the given page.
78
+ # This may be overriden by inherited classes.
79
+ def get_paginated_collection
80
+ @full_collection[@options[:offset], @options[:limit]]
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,7 @@
1
+ module Paginate
2
+ module Fixtures
3
+ class ArModel < ActiveRecord::Base
4
+ extend Paginate::AR
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ DataMapper.setup(:default, 'sqlite3::memory:')
2
+
3
+ module Paginate
4
+ module Fixtures
5
+ class DmModel
6
+ include DataMapper::Resource
7
+ extend Paginate::DM
8
+
9
+ property :id, Serial
10
+ property :name, String
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Paginate::Helpers::Merb do
4
+ before(:all) do
5
+ @klass = Class.new
6
+ @klass.__send__(:include, Paginate::Helpers::Merb)
7
+ @object = @klass.new
8
+ end
9
+
10
+ it 'should include #page_set method from Shared' do
11
+ @object.page_set(5, 10, 2).should ==
12
+ [1,0,3,4,5,6,7,0,10]
13
+ end
14
+
15
+ # Being lazy and just using a mock here.
16
+ describe '#pagination_partial' do
17
+ it 'should call partial' do
18
+ collection = (1..50).to_a
19
+ collection.extend(Paginate::Simple)
20
+ collection = collection.paginate(:page => 5, :limit => 5)
21
+ vars = {
22
+ :current_page => 5,
23
+ :pages => 10,
24
+ :padding => 4
25
+ }
26
+
27
+ @object.should_receive(:partial).with('partial_name', vars)
28
+ @object.pagination_partial(collection, 'partial_name', 4)
29
+ end
30
+ end
31
+
32
+ describe '#page_url' do
33
+ it 'should add page query option' do
34
+ @object.page_url(5, 'path', '').should ==
35
+ 'path?page=5'
36
+ end
37
+
38
+ it 'should leave other query options' do
39
+ @object.page_url(5, 'path', 'limit=10&something=text').should ==
40
+ 'path?page=5&limit=10&something=text'
41
+ end
42
+
43
+ it 'should remove existing page option' do
44
+ # middle
45
+ @object.page_url(5, 'path', 'limit=10&page=10&something=text').should ==
46
+ 'path?page=5&limit=10&something=text'
47
+
48
+ # start
49
+ @object.page_url(5, 'path', 'page=10&limit=10&something=text').should ==
50
+ 'path?page=5&limit=10&something=text'
51
+
52
+ # end
53
+ @object.page_url(5, 'path', 'limit=10&something=text&page=10').should ==
54
+ 'path?page=5&limit=10&something=text'
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,61 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Paginate::Helpers::Shared do
4
+ before(:all) do
5
+ @klass = Class.new
6
+ @klass.__send__(:include, Paginate::Helpers::Shared)
7
+ @object = @klass.new
8
+ end
9
+
10
+ describe '#page_set' do
11
+ it 'should pad around current page' do
12
+ @object.page_set(5, 10, 2).should ==
13
+ [1,0,3,4,5,6,7,0,10]
14
+
15
+ @object.page_set(5, 10, 1).should ==
16
+ [1,0,4,5,6,0,10]
17
+
18
+ @object.page_set(5, 10, 0).should ==
19
+ [1,0,5,0,10]
20
+ end
21
+
22
+ it 'should default padding to 3' do
23
+ @object.page_set(10, 20).should ==
24
+ [1,0,7,8,9,10,11,12,13,0,20]
25
+ end
26
+
27
+ it 'should not repeat first page' do
28
+ @object.page_set(2, 10, 2).should ==
29
+ [1,2,3,4,0,10]
30
+
31
+ @object.page_set(1, 10, 2).should ==
32
+ [1,2,3,0,10]
33
+ end
34
+
35
+ it 'should not repeat last page' do
36
+ @object.page_set(9, 10, 2).should ==
37
+ [1,0,7,8,9,10]
38
+
39
+ @object.page_set(10, 10, 2).should ==
40
+ [1,0,8,9,10]
41
+ end
42
+
43
+ it 'should include 0 when pages skipped' do
44
+ @object.page_set(5, 10, 2).should ==
45
+ [1,0,3,4,5,6,7,0,10]
46
+ end
47
+
48
+ it 'should not include 0 when padding is next to first or last' do
49
+ @object.page_set(3, 10, 2).should ==
50
+ [1,2,3,4,5,0,10]
51
+
52
+ @object.page_set(8, 10, 2).should ==
53
+ [1,0,6,7,8,9,10]
54
+ end
55
+
56
+ it 'should just return [1] if only 1 page' do
57
+ @object.page_set(1, 1, 3).should ==
58
+ [1]
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ require 'active_record'
2
+
3
+ # Setup AR connection, schema
4
+ ActiveRecord::Base.establish_connection(
5
+ :adapter => "sqlite3",
6
+ :dbfile => ":memory:"
7
+ )
8
+
9
+ ActiveRecord::Schema.define do
10
+ create_table :ar_models do |table|
11
+ table.column :name, :string
12
+ end
13
+ end
14
+
15
+ require File.dirname(__FILE__) + '/../spec_helper'
16
+ require File.dirname(__FILE__) + '/../fixtures/ar'
17
+
18
+ describe 'Paginate::AR' do
19
+ before(:each) do
20
+ @model = Paginate::Fixtures::ArModel
21
+ @model.destroy_all
22
+ 1.upto(50) do |i|
23
+ @model.create(:name => "Person #{i}")
24
+ end
25
+ end
26
+
27
+ def paginated(options = {})
28
+ @model.paginate(options)
29
+ end
30
+
31
+ def paginated_collected(options = {})
32
+ @model.paginate(options).collect {|r| r.name}
33
+ end
34
+
35
+ def destroy_all
36
+ @model.destroy_all
37
+ end
38
+
39
+ describe '#paginate' do
40
+ it_should_behave_like "all paginate methods"
41
+ it_should_behave_like "ORM paginate methods"
42
+
43
+ it 'should order per options passed' do
44
+ paginated_collected(:limit => 3, :page => 1, :order => 'id desc').should ==
45
+ ["Person 50", "Person 49", "Person 48"]
46
+ end
47
+
48
+ it 'should accept conditions (representative of options to .all)' do
49
+ collection = paginated(:limit => 3, :page => 1, :conditions => ['name like ?', 'Person 3%'])
50
+ collection.collect {|r| r.name}.should ==
51
+ ["Person 3", "Person 30", "Person 31"]
52
+ collection.pages.should == 4
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "Paginate.config" do
4
+ before(:each) do
5
+ # Force to default state
6
+ Paginate.instance_variable_set(:@config, nil)
7
+ end
8
+
9
+ after(:all) do
10
+ # Force to default state for other specs
11
+ Paginate.instance_variable_set(:@config, nil)
12
+ end
13
+
14
+ it 'should initialize with DEFAULTS' do
15
+ Paginate.config.should == Paginate::DEFAULTS
16
+ end
17
+
18
+ it 'should be writable' do
19
+ Paginate.config[:default_limit] = 1
20
+ Paginate.instance_variable_get(:@config)[:default_limit].should == 1
21
+ end
22
+
23
+ describe ':default_limit' do
24
+ it 'should be used as default limit' do
25
+ Paginate.config[:default_limit] = 3
26
+ ary = (1..20).collect {|i| 'Item #{i}'}
27
+ ary.extend(Paginate::Simple)
28
+ ary.paginate.length.should == 3
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ require 'dm-core'
2
+ require 'dm-aggregates'
3
+ require File.dirname(__FILE__) + '/../spec_helper'
4
+ require File.dirname(__FILE__) + '/../fixtures/dm'
5
+
6
+ describe Paginate::DM do
7
+ before(:all) do
8
+ @model = Paginate::Fixtures::DmModel
9
+ @model.auto_migrate!
10
+ end
11
+
12
+ before(:each) do
13
+ @model.all.destroy!
14
+ 1.upto(50) do |i|
15
+ @model.create(:name => "Person #{i}")
16
+ end
17
+ end
18
+
19
+ def paginated(options = {})
20
+ @model.paginate(options)
21
+ end
22
+
23
+ def paginated_collected(options = {})
24
+ @model.paginate(options).collect {|r| r.name}
25
+ end
26
+
27
+ def destroy_all
28
+ @model.all.destroy!
29
+ end
30
+
31
+ describe '#paginate' do
32
+ it_should_behave_like "all paginate methods"
33
+ it_should_behave_like "ORM paginate methods"
34
+
35
+ it 'should order per options passed' do
36
+ paginated_collected(:limit => 3, :page => 1, :order => [:id.desc]).should ==
37
+ ["Person 50", "Person 49", "Person 48"]
38
+ end
39
+
40
+ it 'should accept conditions (representative of options to .all)' do
41
+ collection = paginated(:limit => 3, :page => 1, :name.like => 'Person 3%')
42
+ collection.collect {|r| r.name}.should ==
43
+ ["Person 3", "Person 30", "Person 31"]
44
+ collection.pages.should == 4
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Paginate::Simple do
4
+ before(:each) do
5
+ @full_collection = []
6
+ 1.upto(50) do |i|
7
+ @full_collection << "Person #{i}"
8
+ end
9
+ @full_collection.extend(Paginate::Simple)
10
+ end
11
+
12
+ def paginated(options = {})
13
+ @full_collection.paginate(options)
14
+ end
15
+
16
+ def paginated_collected(options = {})
17
+ paginated(options)
18
+ end
19
+
20
+ def destroy_all
21
+ 50.times { @full_collection.pop }
22
+ end
23
+
24
+ describe '#paginate' do
25
+ it_should_behave_like "all paginate methods"
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'dm-core'
2
+ require 'dm-aggregates'
3
+ require File.dirname(__FILE__) + '/../spec_helper'
4
+ require File.dirname(__FILE__) + '/../fixtures/dm'
5
+
6
+ describe Paginate::Paginators::ORM do
7
+ Model = Paginate::Fixtures::DmModel
8
+ before(:all) do
9
+ Model.auto_migrate!
10
+ end
11
+
12
+ before(:each) do
13
+ Model.all.destroy!
14
+ 1.upto(50) do |i|
15
+ Model.create(:name => "Person #{i}")
16
+ end
17
+ @model = Model
18
+ @klass = Paginate::Paginators::ORM
19
+ end
20
+
21
+ def paginated(options = {})
22
+ @klass.new(Model, options).paginate
23
+ end
24
+
25
+ def paginated_collected(options = {})
26
+ paginated(options).collect {|r| r.name}
27
+ end
28
+
29
+ def destroy_all
30
+ Model.all.destroy!
31
+ end
32
+
33
+ describe '#paginate' do
34
+ it_should_behave_like "all paginate methods"
35
+ it_should_behave_like "ORM paginate methods"
36
+
37
+ it 'should order per options passed' do
38
+ paginated_collected(:limit => 3, :page => 1, :order => [:id.desc]).should ==
39
+ ["Person 50", "Person 49", "Person 48"]
40
+ end
41
+
42
+ it 'should accept conditions (representative of options to .all)' do
43
+ collection = paginated(:limit => 3, :page => 1, :name.like => 'Person 3%')
44
+ collection.collect {|r| r.name}.should ==
45
+ ["Person 3", "Person 30", "Person 31"]
46
+ collection.pages.should == 4
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Paginate::Paginators::Simple do
4
+ # No instance variables are expected by the shared specs, only a +paginated+
5
+ # and a +destroy_all+ method.
6
+ before(:each) do
7
+ @full_collection = []
8
+ 1.upto(50) do |i|
9
+ @full_collection << "Person #{i}"
10
+ end
11
+ @klass = Paginate::Paginators::Simple
12
+ end
13
+
14
+ # paginated is called by shared specs to get a paginated result for the
15
+ # collection.
16
+ def paginated(options = {})
17
+ @klass.new(@full_collection, options).paginate
18
+ end
19
+
20
+ # paginated_collected returns results in a standard format: It should just
21
+ # return an Array of Strings, such as:
22
+ # ["Person 1", "Person 2"], so #collect may be needed in more complex
23
+ # classes.
24
+ def paginated_collected(options = {})
25
+ paginated(options)
26
+ end
27
+
28
+ # destroy_all is called to make @full_collection an empty set. This may mean
29
+ # that all records need to be deleted in, for example, an ORM Paginator.
30
+ def destroy_all
31
+ @full_collection = []
32
+ end
33
+
34
+ describe '#paginate' do
35
+ it_should_behave_like "all paginate methods"
36
+ end
37
+ end
data/spec/shared.rb ADDED
@@ -0,0 +1,155 @@
1
+ # Specs shared by Paginator classes and Paginate modules. These test that the
2
+ # Paginator (directly or via a module) returns the right results based on
3
+ # +page+ and +limit+ options.
4
+ #
5
+ # +paginated+, +paginated_collected+ and +destroy_all+ methods are expected
6
+ # by specs. A +@model+ instance variable is expected for "ORM paginate methods".
7
+ # See simple_spec.rb for examples.
8
+ #
9
+ # +paginated+ is called by shared specs to get a paginated result for the
10
+ # collection.
11
+ #
12
+ # +paginated_collected+ calls paginate, but returns an Array of Strings. If all
13
+ # results were returned, it should return an Array of
14
+ # ["Person 1", "Person 2", ..., "Person 50"] (50 records total). #collect may be
15
+ # needed in more complex classes. For example:
16
+ #
17
+ # @klass.paginate(@full_collection, options).collect {|r| r.name}
18
+ # Model.paginate(options).collect {|r| r.name}
19
+ #
20
+ # +destroy_all+ is called to make the full collection an empty set. This may
21
+ # mean that all records need to be deleted in, for example, an ORM Paginator.
22
+
23
+ shared_examples_for "all paginate methods" do
24
+ describe ':page option' do
25
+ it 'should return correct results per page (correct offset, limit)' do
26
+ paginated_collected(:page => 1, :limit => 5).should ==
27
+ ["Person 1", "Person 2", "Person 3", "Person 4", "Person 5"]
28
+
29
+ paginated_collected(:page => 2, :limit => 5).should ==
30
+ ["Person 6", "Person 7", "Person 8", "Person 9", "Person 10"]
31
+ end
32
+
33
+ it 'should return page 1 if options[:page] == 0' do
34
+ paginated_collected(:page => 0, :limit => 3).should ==
35
+ ["Person 1", "Person 2", "Person 3"]
36
+ end
37
+
38
+ it 'should return page 1 if options[:page] not set' do
39
+ paginated_collected(:limit => 3, :page => -50).should ==
40
+ ["Person 1", "Person 2", "Person 3"]
41
+ end
42
+
43
+ it 'should return last page if options[:page] > total pages' do
44
+ paginated_collected(:limit => 5, :page => 11).should ==
45
+ ["Person 46", "Person 47", "Person 48", "Person 49", "Person 50"]
46
+
47
+ paginated_collected(:limit => 5, :page => 20).should ==
48
+ ["Person 46", "Person 47", "Person 48", "Person 49", "Person 50"]
49
+ end
50
+
51
+ it 'should return last page for page == -1' do
52
+ paginated_collected(:limit => 5, :page => -1).should ==
53
+ ["Person 46", "Person 47", "Person 48", "Person 49", "Person 50"]
54
+
55
+ paginated_collected(:limit => 3, :page => -1).should ==
56
+ ["Person 49", "Person 50"]
57
+ end
58
+
59
+ it 'should return page from last for negative options[:page]' do
60
+ paginated_collected(:limit => 3, :page => -2).should ==
61
+ ["Person 46", "Person 47", "Person 48"]
62
+
63
+ paginated_collected(:limit => 3, :page => -3).should ==
64
+ ["Person 43", "Person 44", "Person 45"]
65
+ end
66
+
67
+ it 'should return first page for very negative options[:page] values' do
68
+ paginated_collected(:limit => 3, :page => -50).should ==
69
+ ["Person 1", "Person 2", "Person 3"]
70
+ end
71
+
72
+ it 'should have at least one result on the last page' do
73
+ paginated(:limit => 5, :page => 10).should_not be_empty
74
+
75
+ collection = paginated(:limit => 5, :page => 11)
76
+ collection.should_not be_empty
77
+ collection.current_page.should == 10
78
+ end
79
+
80
+ it 'should default limit to config[:default_limit]' do
81
+ paginated().length.should == Paginate.config[:default_limit]
82
+ end
83
+ end
84
+
85
+ describe '#current_page' do
86
+ it 'should be added to collection' do
87
+ collection = paginated(:limit => 10)
88
+ # collection.methods.should include("current_page")
89
+ # Not sure why the above won't work. #singleton_methods doesn't either.
90
+ collection.current_page.should be_kind_of(Integer)
91
+ end
92
+
93
+ it 'should be the number of the current page' do
94
+ paginated(:limit => 10, :page => 2).current_page.should == 2
95
+ paginated(:limit => 10, :page => 5).current_page.should == 5
96
+ end
97
+
98
+ it 'should be 1 if there are no results' do
99
+ destroy_all
100
+ paginated(:limit => 10).current_page.should == 1
101
+ end
102
+
103
+ it 'should be >= 1 and <= #pages (ie actual page, not given page)' do
104
+ paginated(:limit => 10, :page => -100).current_page.should == 1
105
+ paginated(:limit => 10, :page => 100).current_page.should == 5
106
+ paginated(:limit => 10, :page => -1).current_page.should == 5
107
+ paginated(:limit => 10, :page => 0).current_page.should == 1
108
+ end
109
+ end
110
+
111
+ describe '#pages' do
112
+ it 'should be added to collection' do
113
+ collection = paginated(:limit => 10)
114
+ # collection.methods.should include("pages")
115
+ # Not sure why the above won't work. #singleton_methods doesn't either.
116
+ collection.pages.should be_kind_of(Integer)
117
+ end
118
+
119
+ it 'should be the total number of pages for the collection' do
120
+ paginated(:limit => 10).pages.should == 5
121
+ paginated(:limit => 49).pages.should == 2
122
+ paginated(:limit => 50).pages.should == 1
123
+ end
124
+
125
+ it 'should be 1 if there are no results' do
126
+ destroy_all
127
+ paginated(:limit => 10).pages.should == 1
128
+ end
129
+ end
130
+ end
131
+
132
+ # These specs are specific to ORM classes/modules, testing that +all+ receives
133
+ # the correct options.
134
+ #
135
+ # A +@model+ instance variable is expected.
136
+ shared_examples_for "ORM paginate methods" do
137
+ it "should set limit and offset options" do
138
+ limit = 10
139
+
140
+ @model.should_receive(:all).with(:offset => 0, :limit => limit)
141
+ paginated(:page => 1, :limit => limit)
142
+
143
+ @model.should_receive(:all).with(:offset => 0, :limit => limit + 1)
144
+ paginated(:page => 1, :limit => limit + 1)
145
+
146
+ @model.should_receive(:all).with(:offset => 0, :limit => limit + 2)
147
+ paginated(:limit => limit + 2)
148
+
149
+ @model.should_receive(:all).with(:offset => limit, :limit => limit)
150
+ paginated(:page => 2, :limit => limit)
151
+
152
+ @model.should_receive(:all).with(:offset => 40, :limit => limit)
153
+ paginated(:page => -1, :limit => limit)
154
+ end
155
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,6 @@
1
+ require "rubygems"
2
+ require "spec"
3
+
4
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
5
+ require 'paginate'
6
+ require File.join(File.dirname(__FILE__), 'shared')
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jm81-paginate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Jared Morgan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-22 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: (Yet another) Pagination for DataMapper, ActiveRecord, and Array
17
+ email: jmorgan@morgancreative.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.md
24
+ - LICENSE
25
+ - TODO
26
+ files:
27
+ - LICENSE
28
+ - README.md
29
+ - Rakefile
30
+ - TODO
31
+ - lib/helpers
32
+ - lib/helpers/merb.rb
33
+ - lib/helpers/shared.rb
34
+ - lib/paginate
35
+ - lib/paginate/ar.rb
36
+ - lib/paginate/dm.rb
37
+ - lib/paginate/simple.rb
38
+ - lib/paginate.rb
39
+ - lib/paginators
40
+ - lib/paginators/orm.rb
41
+ - lib/paginators/simple.rb
42
+ - spec/fixtures
43
+ - spec/fixtures/ar.rb
44
+ - spec/fixtures/dm.rb
45
+ - spec/helpers
46
+ - spec/helpers/merb_spec.rb
47
+ - spec/helpers/shared_spec.rb
48
+ - spec/paginate
49
+ - spec/paginate/ar_spec.rb
50
+ - spec/paginate/config_spec.rb
51
+ - spec/paginate/dm_spec.rb
52
+ - spec/paginate/simple_spec.rb
53
+ - spec/paginators
54
+ - spec/paginators/orm_spec.rb
55
+ - spec/paginators/simple_spec.rb
56
+ - spec/shared.rb
57
+ - spec/spec.opts
58
+ - spec/spec_helper.rb
59
+ has_rdoc: true
60
+ homepage: http://github.com/jm81/paginate/
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.2.0
82
+ signing_key:
83
+ specification_version: 2
84
+ summary: (Yet another) Pagination for DataMapper, ActiveRecord, and Array
85
+ test_files: []
86
+