folio-pagination 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.
Files changed (39) hide show
  1. data/.gitignore +17 -0
  2. data/.travis.yml +3 -0
  3. data/Gemfile +16 -0
  4. data/Guardfile +5 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +235 -0
  7. data/Rakefile +11 -0
  8. data/folio-pagination.gemspec +27 -0
  9. data/lib/folio/core_ext/enumerable.rb +51 -0
  10. data/lib/folio/invalid_page.rb +5 -0
  11. data/lib/folio/ordinal/page.rb +107 -0
  12. data/lib/folio/ordinal.rb +33 -0
  13. data/lib/folio/page.rb +86 -0
  14. data/lib/folio/per_page.rb +15 -0
  15. data/lib/folio/rails.rb +35 -0
  16. data/lib/folio/version.rb +3 -0
  17. data/lib/folio/will_paginate/active_record.rb +132 -0
  18. data/lib/folio/will_paginate/collection.rb +19 -0
  19. data/lib/folio/will_paginate/view_helpers/action_view.rb +10 -0
  20. data/lib/folio/will_paginate/view_helpers/link_renderer.rb +54 -0
  21. data/lib/folio/will_paginate/view_helpers/link_renderer_base.rb +44 -0
  22. data/lib/folio/will_paginate/view_helpers.rb +57 -0
  23. data/lib/folio-pagination.rb +3 -0
  24. data/lib/folio.rb +84 -0
  25. data/test/folio/core_ext/enumerable_test.rb +55 -0
  26. data/test/folio/ordinal/page_test.rb +173 -0
  27. data/test/folio/ordinal_test.rb +57 -0
  28. data/test/folio/page_test.rb +198 -0
  29. data/test/folio/per_page_test.rb +49 -0
  30. data/test/folio/rails_test.rb +15 -0
  31. data/test/folio/version_test.rb +8 -0
  32. data/test/folio/will_paginate/active_record_test.rb +118 -0
  33. data/test/folio/will_paginate/collection_test.rb +43 -0
  34. data/test/folio/will_paginate/view_helpers/link_renderer_base_test.rb +58 -0
  35. data/test/folio/will_paginate/view_helpers/link_renderer_test.rb +58 -0
  36. data/test/folio/will_paginate/view_helpers_test.rb +49 -0
  37. data/test/folio_test.rb +192 -0
  38. data/test/setup/active_record.rb +13 -0
  39. metadata +133 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'guard-minitest'
7
+ end
8
+
9
+ group :will_paginate do
10
+ gem 'rails', '>= 3.0'
11
+ gem 'will_paginate', '~> 3.0'
12
+ end
13
+
14
+ group :test do
15
+ gem 'sqlite3'
16
+ end
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :minitest do
2
+ watch(%r{^test/(.*)_test\.rb})
3
+ watch(%r{^lib/(.*)\.rb}) { |m| "test/#{m[1]}_test.rb" }
4
+ watch(%r{^test/test_helper\.rb}) { 'test' }
5
+ end
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
+ [![Build Status](https://travis-ci.org/instructure/folio.png?branch=master)](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,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,27 @@
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"
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
+ spec.homepage = "https://github.com/instructure/folio"
18
+ spec.license = "MIT"
19
+
20
+ spec.files = `git ls-files`.split($/)
21
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ # Raised when a value passed to a Folio's paginate method is
2
+ # non-sensical or out of bounds for that folio.
3
+ module Folio
4
+ class InvalidPage < ArgumentError; end
5
+ end
@@ -0,0 +1,107 @@
1
+ require 'folio/page'
2
+
3
+ # Mix in to an Enumerable to provide the same methods as Folio::Page but with
4
+ # the following overrides:
5
+ #
6
+ # * ordinal_pages is always true
7
+ #
8
+ # * first_page is always 1
9
+ #
10
+ # * current_page is forced to an integer
11
+ #
12
+ # * previous_page is always either current_page-1 or nil, depending on how
13
+ # current_page relates to first_page.
14
+ #
15
+ # * next_page can only be set if total_pages is unknown. if total_pages is
16
+ # known, next_page will be either current_page+1 or nil, depending on how
17
+ # current_page relates to last_page. if total_pages is unknown and next_page
18
+ # is unset (vs. explicitly set to nil), it will default to current_page+1.
19
+ # if next_page is set to a non-nil value, that value will be forced to an
20
+ # integer.
21
+ #
22
+ # * last_page is deterministic: always total_pages if total_pages is known,
23
+ # current_page if total_pages is unknown and next_page is nil, nil otherwise
24
+ # (indicating the page sequence continues until next_page is nil).
25
+ module Folio
26
+ module Ordinal
27
+ module Page
28
+ def ordinal_pages
29
+ true
30
+ end
31
+ alias :ordinal_pages? :ordinal_pages
32
+
33
+ def first_page
34
+ 1
35
+ end
36
+
37
+ def last_page
38
+ (total_pages || next_page) ? total_pages : current_page
39
+ end
40
+
41
+ def current_page=(value)
42
+ @current_page = value.to_i
43
+ end
44
+
45
+ def next_page=(value)
46
+ @next_page = value && value.to_i
47
+ end
48
+
49
+ def next_page
50
+ if total_pages && current_page >= total_pages
51
+ # known number of pages and we've reached the last one. no next page
52
+ # (even if explicitly set)
53
+ nil
54
+ elsif total_pages || !defined?(@next_page)
55
+ # (1) known number of pages and we haven't reached the last one
56
+ # (because we're not in the branch above), or
57
+ # (2) unknown number of pages, but nothing set, so we assume an
58
+ # infinite stream
59
+ # so there's a next page, and it's the one after this one
60
+ current_page + 1
61
+ else
62
+ # just use what they set
63
+ @next_page
64
+ end
65
+ end
66
+
67
+ def previous_page
68
+ current_page > first_page ? current_page - 1 : nil
69
+ end
70
+
71
+ def out_of_bounds?
72
+ (current_page < first_page) || (last_page && current_page > last_page) || false
73
+ end
74
+
75
+ def offset
76
+ (current_page - 1) * per_page
77
+ end
78
+
79
+ class Decorator < Folio::Page::Decorator
80
+ include Folio::Ordinal::Page
81
+ end
82
+
83
+ class DecoratedArray < Decorator
84
+ def initialize
85
+ super []
86
+ end
87
+
88
+ def replace(array)
89
+ result = super
90
+ if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
91
+ self.total_entries = offset + length
92
+ end
93
+ result
94
+ end
95
+ end
96
+
97
+ def self.decorate(collection)
98
+ collection = Folio::Ordinal::Page::Decorator.new(collection) unless collection.is_a?(Folio::Ordinal::Page)
99
+ collection
100
+ end
101
+
102
+ def self.create
103
+ Folio::Ordinal::Page::DecoratedArray.new
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,33 @@
1
+ require 'folio'
2
+ require 'folio/ordinal/page'
3
+ require 'folio/invalid_page'
4
+
5
+ # Mix in to a source to provide the same methods as Folio, but with simpler
6
+ # build_page and fill_page methods required on the host (some responsibility is
7
+ # moved into the paginate method).
8
+ #
9
+ # * build_page no longer needs to configure ordinal_page?, first_page,
10
+ # or last_page on the instantiated page. Instead, just instantiate
11
+ # and return a Folio::Ordinal::Page. If what you return is not a
12
+ # Folio::Ordinal::Page, paginate will decorate it to be one.
13
+ #
14
+ # * fill_page no longer needs to configure next_page and previous_page; the
15
+ # ordinal page will handle them. (Note that if necessary, you can still set
16
+ # next_page explicitly to nil.) Also, paginate will now perform ordinal
17
+ # bounds checking for you, so you can focus entirely on populating the page.
18
+ #
19
+ module Folio
20
+ module Ordinal
21
+ # decorate the page before configuring, and then validate the configured
22
+ # current_page before returning it
23
+ def configure_pagination(page, options)
24
+ page = super(::Folio::Ordinal::Page.decorate(page), options)
25
+ raise ::Folio::InvalidPage unless page.current_page.is_a?(Integer)
26
+ raise ::Folio::InvalidPage if page.out_of_bounds?
27
+ page
28
+ end
29
+
30
+ # otherwise acts like a normal folio
31
+ include ::Folio
32
+ end
33
+ end
data/lib/folio/page.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'folio/per_page'
2
+ require 'delegate'
3
+
4
+ # Mix into any Enumerable. The mixin gives you the eight attributes and
5
+ # one method described below.
6
+ #
7
+ # ordinal_pages?, first_page, and last_page are common to all pages
8
+ # created by a folio and are configured, as available, when the folio
9
+ # creates a blank page.
10
+ #
11
+ # current_page, per_page, and total_entries control the filling of a
12
+ # page and are configured from parameters to the folio's paginate
13
+ # method.
14
+ #
15
+ # next_page and previous_page are configured, as available, when the
16
+ # folio fills the configured page.
17
+ module Folio
18
+ module Page
19
+ # indicates whether the page identifiers in current_page,
20
+ # first_page, last_page, previous_page, and next_page should be
21
+ # considered ordinal or not.
22
+ attr_accessor :ordinal_pages
23
+ alias :ordinal_pages? :ordinal_pages
24
+
25
+ # page identifier addressing this page within the folio.
26
+ attr_accessor :current_page
27
+
28
+ # number of items requested from the folio when filling the page.
29
+ include Folio::PerPage
30
+
31
+ # page identifier addressing the first page within the folio.
32
+ attr_accessor :first_page
33
+
34
+ # page identifier addressing the final page within the folio, if
35
+ # known.
36
+ def last_page=(value)
37
+ @last_page = value
38
+ end
39
+
40
+ def last_page
41
+ if next_page.nil?
42
+ current_page
43
+ else
44
+ @last_page
45
+ end
46
+ end
47
+
48
+ # page identifier addressing the immediately following page within
49
+ # the folio, if there is one.
50
+ attr_accessor :next_page
51
+
52
+ # page identifier addressing the immediately preceding page within
53
+ # the folio, if there is one and it is known.
54
+ attr_accessor :previous_page
55
+
56
+ # number of items in the folio, if known.
57
+ attr_accessor :total_entries
58
+
59
+ # number of pages in the folio, if known. calculated from
60
+ # total_entries and per_page.
61
+ def total_pages
62
+ return nil unless total_entries && per_page && per_page > 0
63
+ return 1 if total_entries <= 0
64
+ (total_entries / per_page.to_f).ceil
65
+ end
66
+
67
+ class Decorator < ::SimpleDelegator
68
+ include Folio::Page
69
+ end
70
+
71
+ class DecoratedArray < Decorator
72
+ def initialize
73
+ super []
74
+ end
75
+ end
76
+
77
+ def self.decorate(collection)
78
+ collection = Folio::Page::Decorator.new(collection) unless collection.is_a?(Folio::Page)
79
+ collection
80
+ end
81
+
82
+ def self.create
83
+ Folio::Page::DecoratedArray.new
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,15 @@
1
+ module Folio
2
+ module PerPage
3
+ def default_per_page
4
+ Folio.per_page
5
+ end
6
+
7
+ def per_page(*args)
8
+ raise ArgumentError if args.size > 1
9
+ @per_page = (args.first && args.first.to_i) if args.size > 0
10
+ @per_page ? @per_page : default_per_page
11
+ end
12
+
13
+ alias_method :per_page=, :per_page
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ begin
2
+ require 'rails'
3
+ rescue LoadError
4
+ raise "folio-pagination's rails support requires rails"
5
+ end
6
+
7
+ begin
8
+ require 'will_paginate/railtie'
9
+ rescue LoadError
10
+ raise "folio-pagination's rails support requires will_paginate"
11
+ end
12
+
13
+ module Folio
14
+ module WillPaginate
15
+ class Railtie < Rails::Railtie
16
+ config.action_dispatch.rescue_responses.merge!(
17
+ 'Folio::InvalidPage' => :not_found
18
+ )
19
+
20
+ initializer "folio" do |app|
21
+ ActiveSupport.on_load :active_record do
22
+ require 'folio/will_paginate/active_record'
23
+ end
24
+
25
+ ActiveSupport.on_load :action_view do
26
+ require 'folio/will_paginate/view_helpers/action_view'
27
+ end
28
+
29
+ # don't need early access to our stuff, but will_paginate loaded
30
+ # theirs, so we better patch it up now rather than later
31
+ require 'folio/will_paginate/view_helpers'
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Folio
2
+ VERSION = "0.0.2"
3
+ end