folio-pagination-legacy 0.0.2
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/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +16 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +235 -0
- data/Rakefile +11 -0
- data/folio-pagination-legacy.gemspec +30 -0
- data/lib/folio-pagination-legacy.rb +3 -0
- data/lib/folio.rb +84 -0
- data/lib/folio/core_ext/enumerable.rb +51 -0
- data/lib/folio/invalid_page.rb +5 -0
- data/lib/folio/ordinal.rb +33 -0
- data/lib/folio/ordinal/page.rb +107 -0
- data/lib/folio/page.rb +86 -0
- data/lib/folio/per_page.rb +15 -0
- data/lib/folio/rails.rb +21 -0
- data/lib/folio/version.rb +3 -0
- data/lib/folio/will_paginate/active_record.rb +71 -0
- data/lib/folio/will_paginate/array.rb +10 -0
- data/lib/folio/will_paginate/collection.rb +19 -0
- data/lib/folio/will_paginate/view_helpers.rb +59 -0
- data/lib/folio/will_paginate/view_helpers/action_view.rb +10 -0
- data/lib/folio/will_paginate/view_helpers/link_renderer.rb +53 -0
- data/test/folio/core_ext/enumerable_test.rb +55 -0
- data/test/folio/ordinal/page_test.rb +173 -0
- data/test/folio/ordinal_test.rb +57 -0
- data/test/folio/page_test.rb +198 -0
- data/test/folio/per_page_test.rb +49 -0
- data/test/folio/rails_test.rb +11 -0
- data/test/folio/version_test.rb +8 -0
- data/test/folio/will_paginate/active_record_test.rb +113 -0
- data/test/folio/will_paginate/collection_test.rb +43 -0
- data/test/folio/will_paginate/view_helpers/link_renderer_test.rb +160 -0
- data/test/folio/will_paginate/view_helpers_test.rb +49 -0
- data/test/folio_test.rb +192 -0
- data/test/setup/active_record.rb +13 -0
- metadata +133 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Instructure, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# Folio
|
2
|
+
|
3
|
+
`Folio` is a library for pagination. It's meant to be nearly compatible
|
4
|
+
with `WillPaginate`, but with broader -- yet more well-defined --
|
5
|
+
semantics to allow for sources whose page identifiers are non-ordinal
|
6
|
+
(i.e. a page identifier of `3` does not necessarily indicate the third
|
7
|
+
page).
|
8
|
+
|
9
|
+
[](https://travis-ci.org/instructure/folio)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'folio-pagination'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install folio-pagination
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
The core `Folio` interface is defined by two mixins. Mixing `Folio` into
|
28
|
+
a source of items creates a "folio" and provides pagination on that
|
29
|
+
folio. Mixing `Folio::Page` into a subset of items from a folio creates
|
30
|
+
a "page" with additional properties relating it to the folio and the
|
31
|
+
other pages in the folio.
|
32
|
+
|
33
|
+
`Folio` also provides some basic implementations, both standalone and by
|
34
|
+
mixing these modules in to familiar classes.
|
35
|
+
|
36
|
+
### Pages
|
37
|
+
|
38
|
+
You can mix `Folio::Page` into any `Enumerable`. The mixin gives you
|
39
|
+
eight attributes and one method:
|
40
|
+
|
41
|
+
* `ordinal_pages?` indicates whether the page identifiers in
|
42
|
+
`current_page`, `first_page`, `last_page`, `previous_page`, and
|
43
|
+
`next_page` should be considered ordinal or not.
|
44
|
+
|
45
|
+
* `current_page` is the page identifier addressing this page within the
|
46
|
+
folio.
|
47
|
+
|
48
|
+
* `per_page` is the number of items requested from the folio when
|
49
|
+
filling this page.
|
50
|
+
|
51
|
+
* `first_page` is the page identifier addressing the first page within
|
52
|
+
the folio.
|
53
|
+
|
54
|
+
* `last_page` is the page identifier addressing the final page within
|
55
|
+
the folio, if known.
|
56
|
+
|
57
|
+
* `next_page` is the page identifier addressing the immediately
|
58
|
+
following page within the folio, if there is one.
|
59
|
+
|
60
|
+
* `previous_page` is the page identifier addressing the immediately
|
61
|
+
preceding page within the folio, if there is one and it is known.
|
62
|
+
|
63
|
+
* `total_entries` is the number of items in the folio, if known.
|
64
|
+
|
65
|
+
* `total_pages` if the number of pages in the folio, if known. It is
|
66
|
+
calculated from `total_entries` and `per_page`.
|
67
|
+
|
68
|
+
`ordinal_pages?`, `first_page`, and `last_page` are common to all pages
|
69
|
+
created by a folio and are configured, as available, when the folio
|
70
|
+
creates a blank page in its `build_page` method (see below).
|
71
|
+
|
72
|
+
`current_page`, `per_page`, and `total_entries` control the filling of a
|
73
|
+
page and are configured from parameters to the folio's `paginate`
|
74
|
+
method.
|
75
|
+
|
76
|
+
`next_page` and `previous_page` are configured, as available, when the
|
77
|
+
folio fills the configured page in its `fill_page` method (see below).
|
78
|
+
|
79
|
+
### Folios
|
80
|
+
|
81
|
+
You can mix `Folio` into any class implementing two methods:
|
82
|
+
|
83
|
+
* `build_page` is responsible for instantiating a `Folio::Page` and
|
84
|
+
configuring its `ordinal_pages?`, `first_page`, and `last_page`
|
85
|
+
attributes.
|
86
|
+
|
87
|
+
* `fill_page` will receive a `Folio::Page` with the `ordinal_pages?`,
|
88
|
+
`first_page`, `last_page`, `current_page`, `per_page`, and
|
89
|
+
`total_entries` attributes configured, and should populate the page
|
90
|
+
with the corresponding items from the folio. It should also set
|
91
|
+
appropriate values for the `next_page` and `previous_page` attributes
|
92
|
+
on the page. If the value provided in the page's `current_page`
|
93
|
+
cannot be interpreted as addressing a page in the folio,
|
94
|
+
`Folio::InvalidPage` should be raised.
|
95
|
+
|
96
|
+
In return, `Folio` provides a `paginate` method and `per_page`
|
97
|
+
attributes for both the folio class and for individual folio instances.
|
98
|
+
|
99
|
+
The `paginate` method coordinates the page creation, configuration, and
|
100
|
+
population. It takes three parameters: `page`, `per_page`, and
|
101
|
+
`total_entries`, each optional.
|
102
|
+
|
103
|
+
* `page` configures the page's `current_page`, defaulting to the page's
|
104
|
+
`first_page`.
|
105
|
+
|
106
|
+
* `per_page` configures the page's `per_page`, defaulting to the
|
107
|
+
folio's `per_page` attribute.
|
108
|
+
|
109
|
+
* `total_entries` configures the page's `total_entries`, if present.
|
110
|
+
otherwise, if the folio implements a `count` method, the page's
|
111
|
+
`total_entries` will be set from that method.
|
112
|
+
|
113
|
+
NOTE: providing a `total_entries` parameter of nil will still bypass the
|
114
|
+
`count` method, leaving `total_entries` nil. This is useful when the
|
115
|
+
count would be too expensive and you'd rather just leave the number of
|
116
|
+
entries unknown.
|
117
|
+
|
118
|
+
The `per_page` attribute added to the folio instance will default to the
|
119
|
+
`per_page` attribute from the folio class when unset. The `per_page`
|
120
|
+
class attribute added to the folio class will default to global
|
121
|
+
`Folio.per_page` when unset.
|
122
|
+
|
123
|
+
### Ordinal Pages and Folios
|
124
|
+
|
125
|
+
A typical use case for pagination deals with ordinal page identifiers;
|
126
|
+
e.g. "1" means the first page, "2" means the second page, etc.
|
127
|
+
|
128
|
+
As a matter of convenience for these use cases, additional mixins of
|
129
|
+
`Folio::Ordinal` and `Folio::Ordinal::Page` are provided.
|
130
|
+
|
131
|
+
Mixing `Folio::Ordinal::Page` into an `Enumerable` provides the same
|
132
|
+
methods as `Folio::Page` but with the following overrides:
|
133
|
+
|
134
|
+
* `ordinal_pages` is always true
|
135
|
+
|
136
|
+
* `first_page` is always 1
|
137
|
+
|
138
|
+
* `previous_page` is always either `current_page-1` or nil, depending
|
139
|
+
on how `current_page` relates to `first_page`.
|
140
|
+
|
141
|
+
* `next_page` can only be set if `total_pages` is unknown. if
|
142
|
+
`total_pages` is known, `next_page` will be either `current_page+1`
|
143
|
+
or nil, depending on how `current_page` relates to `last_page`. if
|
144
|
+
`total_pages` is unknown and `next_page` is unset (vs. explicitly set
|
145
|
+
to nil), it will default to `current_page+1`.
|
146
|
+
|
147
|
+
* `last_page` is deterministic: always `total_pages` if `total_pages`
|
148
|
+
is known, `current_page` if `total_pages` is unknown and `next_page`
|
149
|
+
is nil, nil otherwise (indicating the page sequence continues until
|
150
|
+
`next_page` is nil).
|
151
|
+
|
152
|
+
Similarly, mixing `Folio::Ordinal` into a source provides the same
|
153
|
+
methods as `Folio`, but simplifies your `build_page` and `fill_page`
|
154
|
+
methods by moving some responsibility into the `paginate` method.
|
155
|
+
|
156
|
+
* `build_page` no longer needs to configure `ordinal_page?`, `first_page`,
|
157
|
+
or `last_page` on the instantiated page. Instead, just instantiate
|
158
|
+
and return a `Folio::Page` or `Folio::Ordinal::Page`. If what you
|
159
|
+
return is not a `Folio::Ordinal::Page`, `paginate` will decorate it
|
160
|
+
to be one. Then `ordinal_page?`, `first_page`, and `last_page` are
|
161
|
+
handled for you, as described above.
|
162
|
+
|
163
|
+
* `fill_page` no longer needs to configure `next_page` and
|
164
|
+
`previous_page`; the ordinal page will handle them. (Note that if
|
165
|
+
necessary, you can still set `next_page` explicitly to nil.) Also,
|
166
|
+
`paginate` will now perform ordinal bounds checking for you, so you
|
167
|
+
can focus entirely on populating the page.
|
168
|
+
|
169
|
+
### Decorations and `create`
|
170
|
+
|
171
|
+
Often times you just want to take a simple collection, such as an
|
172
|
+
array, and have it act like a page. One way would be to subclass
|
173
|
+
`Array` and mixin `Folio::Page`, then instantiate the subclass.
|
174
|
+
|
175
|
+
Alternately, you can just call `Folio::Page.decorate(collection)`. This
|
176
|
+
will decorate the collection instance in a delegate that mixes in
|
177
|
+
`Folio::Page` for you. So, for example, a simple `build_page` method
|
178
|
+
could just be:
|
179
|
+
|
180
|
+
```
|
181
|
+
def build_page
|
182
|
+
Folio::Page.decorate([])
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
For the common case where a new empty array is what you intend to
|
187
|
+
decorate, you can simply call `Folio::Page.create`.
|
188
|
+
|
189
|
+
`Folio::Page::Ordinal.decorate(collection)` and
|
190
|
+
`Folio::Page::Ordinal.create` are also available, respectively, for the
|
191
|
+
ordinal case. `decorate` will assume the passed in collection lacks any
|
192
|
+
pagination and will include `Folio::Page` along with
|
193
|
+
`Folio::Page::Ordinal`.
|
194
|
+
|
195
|
+
### Enumerable Extension
|
196
|
+
|
197
|
+
If you require `folio/core_ext/enumerable`, all `Enumerable`s will be
|
198
|
+
extended with `Folio::Ordinal` and naive `build_page` and `fill_page`
|
199
|
+
methods.
|
200
|
+
|
201
|
+
`build_page` will simply return a basic ordinal page as from
|
202
|
+
`Folio::Page::Ordinal.create`. `fill_page` then selects an appropriate
|
203
|
+
range of items from the folio using standard `Enumerable` methods, then
|
204
|
+
calls `replace` on the page (it's a decorated array) with this subset.
|
205
|
+
|
206
|
+
This lets you do things like:
|
207
|
+
|
208
|
+
```
|
209
|
+
require 'folio/core_ext/enumerable'
|
210
|
+
|
211
|
+
natural_numbers = Enumerator.new do |enum|
|
212
|
+
n = 0
|
213
|
+
loop{ enum.yield(n += 1) }
|
214
|
+
end
|
215
|
+
page = natural_numbers.paginate(page: 3, per_page: 5, total_entries: nil)
|
216
|
+
|
217
|
+
page.ordinal_pages? #=> true
|
218
|
+
page.per_page #=> 5
|
219
|
+
page.first_page #=> 1
|
220
|
+
page.previous_page #=> 2
|
221
|
+
page.current_page #=> 3
|
222
|
+
page.next_page #=> 4
|
223
|
+
page.last_page #=> nil
|
224
|
+
page.total_entries #=> nil
|
225
|
+
page.total_pages #=> nil
|
226
|
+
page #=> [11, 12, 13, 14, 15]
|
227
|
+
```
|
228
|
+
|
229
|
+
## Contributing
|
230
|
+
|
231
|
+
1. Fork it
|
232
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
233
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
234
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
235
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'folio/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "folio-pagination-legacy"
|
8
|
+
spec.version = Folio::VERSION
|
9
|
+
spec.authors = ["Jacob Fugal"]
|
10
|
+
spec.email = ["jacob@instructure.com"]
|
11
|
+
spec.description = %q{A pagination library.}
|
12
|
+
spec.summary = %q{
|
13
|
+
Folio is a library for pagination. It's meant to be nearly compatible
|
14
|
+
with WillPaginate, but with broader -- yet more well-defined -- semantics
|
15
|
+
to allow for sources whose page identifiers are non-ordinal.
|
16
|
+
|
17
|
+
This version of the gem is for legacy Rails 2.3 support. If you're running
|
18
|
+
Rails 3 or newer, you want the folio-pagination gem.
|
19
|
+
}
|
20
|
+
spec.homepage = "https://github.com/instructure/folio/tree/legacy"
|
21
|
+
spec.license = "MIT"
|
22
|
+
|
23
|
+
spec.files = `git ls-files`.split($/)
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
end
|
data/lib/folio.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require "folio/version"
|
2
|
+
require "folio/per_page"
|
3
|
+
|
4
|
+
# Mix into any class implementing the following two methods:
|
5
|
+
#
|
6
|
+
# +build_page+: Responsible for instantiating a Folio::Page and
|
7
|
+
# configuring its ordinal_pages?, first_page, and last_page attributes;
|
8
|
+
# those values being common to any page returned from the folio.
|
9
|
+
#
|
10
|
+
# +fill_page+: Receives a Folio::Page with the ordinal_pages?,
|
11
|
+
# first_page, last_page, current_page, per_page, and total_entries
|
12
|
+
# attributes configured, and populates the page with the corresponding
|
13
|
+
# items from the folio. Also sets appropriate values for the next_page
|
14
|
+
# and previous_page attributes on the page. If the value provided in the
|
15
|
+
# page's current_page cannot be interpreted as addressing a page in the
|
16
|
+
# folio, raises Folio::InvalidPage.
|
17
|
+
#
|
18
|
+
# In return, `Folio` provides a the paginate method and per_page
|
19
|
+
# attributes described below.
|
20
|
+
module Folio
|
21
|
+
# Returns a page worth of items from the folio in a Folio::Page.
|
22
|
+
# accepts the following parameters:
|
23
|
+
#
|
24
|
+
# +page+: a page identifier addressing which page of the folio to
|
25
|
+
# return. if not present, the first page will be returned. will raise
|
26
|
+
# Folio::InvalidPage if the provided value cannot be used to address a
|
27
|
+
# page.
|
28
|
+
#
|
29
|
+
# +per_page+: number of items to attempt to include in the page. if
|
30
|
+
# not present, defaults to the folio's per_page value. should only
|
31
|
+
# include fewer items if the end of the folio is reached.
|
32
|
+
#
|
33
|
+
# +total_entries+: pre-calculated value for the total number of items
|
34
|
+
# in the folio. may be nil, indicating the returned page should have
|
35
|
+
# total_entries nil.
|
36
|
+
#
|
37
|
+
# if the folio implements a count method and the total_entries
|
38
|
+
# parameter is not supplied, the page's total_entries will be set from
|
39
|
+
# the count method.
|
40
|
+
def paginate(options={})
|
41
|
+
page = self.build_page
|
42
|
+
page = self.configure_pagination(page, options)
|
43
|
+
page = self.fill_page(page)
|
44
|
+
page
|
45
|
+
end
|
46
|
+
|
47
|
+
def configure_pagination(page, options)
|
48
|
+
current_page = options.fetch(:page) { nil }
|
49
|
+
current_page = page.first_page if current_page.nil?
|
50
|
+
page.current_page = current_page
|
51
|
+
page.per_page = options.fetch(:per_page) { self.per_page }
|
52
|
+
page.total_entries = options.fetch(:total_entries) { self.respond_to?(:count) ? self.count : nil }
|
53
|
+
page
|
54
|
+
end
|
55
|
+
|
56
|
+
def default_per_page
|
57
|
+
self.class.per_page
|
58
|
+
end
|
59
|
+
|
60
|
+
include ::Folio::PerPage
|
61
|
+
|
62
|
+
# this funny pattern is so that if a module (e.g. Folio::Ordinal)
|
63
|
+
# includes this module, it won't get the per_page attribute, but will
|
64
|
+
# still be able to bestow that attribute on any class that includes
|
65
|
+
# *it*.
|
66
|
+
module PerPageIncluder
|
67
|
+
def included(klass)
|
68
|
+
if klass.is_a?(Class)
|
69
|
+
klass.extend ::Folio::PerPage
|
70
|
+
else
|
71
|
+
klass.extend ::Folio::PerPageIncluder
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
extend PerPageIncluder
|
77
|
+
extend PerPage
|
78
|
+
per_page(30)
|
79
|
+
end
|
80
|
+
|
81
|
+
# load the other commonly used portions of the gem
|
82
|
+
require "folio/page"
|
83
|
+
require "folio/ordinal"
|
84
|
+
require "folio/ordinal/page"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'folio/ordinal'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module Folio
|
5
|
+
module Enumerable
|
6
|
+
class Decorator < ::SimpleDelegator
|
7
|
+
# just fill a typical ordinal page
|
8
|
+
def build_page
|
9
|
+
::Folio::Ordinal::Page.create
|
10
|
+
end
|
11
|
+
|
12
|
+
# fill by taking the appropriate slice out of the enumerable. if
|
13
|
+
# the slice is empty and it's not the first page, it's invalid
|
14
|
+
def fill_page(page)
|
15
|
+
slice = self.each_slice(page.per_page).first(page.current_page)[page.current_page-1] || []
|
16
|
+
raise ::Folio::InvalidPage if slice.empty? && page.current_page != page.first_page
|
17
|
+
page.replace slice
|
18
|
+
page
|
19
|
+
end
|
20
|
+
|
21
|
+
# things that already included Enumerable won't have extended the
|
22
|
+
# PerPage, so the instance's default default_per_page method looking
|
23
|
+
# at self.class.per_page won't work. point it back at
|
24
|
+
# Folio.per_page
|
25
|
+
def default_per_page
|
26
|
+
Folio.per_page
|
27
|
+
end
|
28
|
+
|
29
|
+
include ::Folio::Ordinal
|
30
|
+
|
31
|
+
METHODS = self.instance_methods - SimpleDelegator.instance_methods
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Extends any Enumerable to act like a Folio::Ordinal. Important: the
|
37
|
+
# source Enumerable is still not a Folio::Ordinal (it's not in the
|
38
|
+
# ancestors list). Instead, any Enumerable can be decorated via its new
|
39
|
+
# `pagination_decorated` method, and all folio methods are forwarded to
|
40
|
+
# that decorated version.
|
41
|
+
module Enumerable
|
42
|
+
def pagination_decorated
|
43
|
+
@pagination_decorated ||= Folio::Enumerable::Decorator.new(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
Folio::Enumerable::Decorator::METHODS.each do |method|
|
47
|
+
define_method(method) do |*args|
|
48
|
+
pagination_decorated.send(method, *args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|