jm81-paginate 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+