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.
Files changed (38) 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-legacy.gemspec +30 -0
  9. data/lib/folio-pagination-legacy.rb +3 -0
  10. data/lib/folio.rb +84 -0
  11. data/lib/folio/core_ext/enumerable.rb +51 -0
  12. data/lib/folio/invalid_page.rb +5 -0
  13. data/lib/folio/ordinal.rb +33 -0
  14. data/lib/folio/ordinal/page.rb +107 -0
  15. data/lib/folio/page.rb +86 -0
  16. data/lib/folio/per_page.rb +15 -0
  17. data/lib/folio/rails.rb +21 -0
  18. data/lib/folio/version.rb +3 -0
  19. data/lib/folio/will_paginate/active_record.rb +71 -0
  20. data/lib/folio/will_paginate/array.rb +10 -0
  21. data/lib/folio/will_paginate/collection.rb +19 -0
  22. data/lib/folio/will_paginate/view_helpers.rb +59 -0
  23. data/lib/folio/will_paginate/view_helpers/action_view.rb +10 -0
  24. data/lib/folio/will_paginate/view_helpers/link_renderer.rb +53 -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 +11 -0
  31. data/test/folio/version_test.rb +8 -0
  32. data/test/folio/will_paginate/active_record_test.rb +113 -0
  33. data/test/folio/will_paginate/collection_test.rb +43 -0
  34. data/test/folio/will_paginate/view_helpers/link_renderer_test.rb +160 -0
  35. data/test/folio/will_paginate/view_helpers_test.rb +49 -0
  36. data/test/folio_test.rb +192 -0
  37. data/test/setup/active_record.rb +13 -0
  38. 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', '~> 2.3.6'
11
+ gem 'will_paginate', '~> 2.3'
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,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
@@ -0,0 +1,3 @@
1
+ # convenience so that you can require by the gem name and get the same effect
2
+ # as requiring the canonical 'folio'.
3
+ require 'folio'
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