folio-pagination 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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