folio-pagination-legacy 0.0.2

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