geared_pagination 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 86fef30f9fb2e850e8b305a079d05d39007ec6bd
4
+ data.tar.gz: 83e91624d0fbd65747604f91628e826f00a131b0
5
+ SHA512:
6
+ metadata.gz: f7e00eb4a7598860c873eb1810ffdba1c8e5a30d625d42b6a7e10f035786e593724b74c8c496089495ada18c992af7b41534041e1b5951f8573932a3d533509e
7
+ data.tar.gz: c2feb3068a763034a474fca15cd793b3fdeae695cee4c6f0187c4ea2918a76a24f9a124061407922a3b397137119fdc234413153563ba0ed9f3301af9204238e
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'byebug'
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ geared_pagination (0.1)
5
+ activesupport (>= 5.0)
6
+ addressable (>= 2.5.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (5.1.0.beta1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (~> 0.7)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ addressable (2.5.0)
17
+ public_suffix (~> 2.0, >= 2.0.2)
18
+ byebug (9.0.6)
19
+ concurrent-ruby (1.0.4)
20
+ i18n (0.8.1)
21
+ minitest (5.10.1)
22
+ public_suffix (2.0.5)
23
+ rake (12.0.0)
24
+ thread_safe (0.3.5)
25
+ tzinfo (1.2.2)
26
+ thread_safe (~> 0.1)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler (~> 1.12)
33
+ byebug
34
+ geared_pagination!
35
+ rake
36
+
37
+ BUNDLED WITH
38
+ 1.14.6
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 David Heinemeier Hansson, Basecamp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # Geared Pagination
2
+
3
+ Most pagination schemes use a fixed page size. Page 1 returns as many elements as page 2. But that's
4
+ frequently not the most sensible way to page through a large collection when you care about serving the
5
+ initial request as quickly as possible. This is particularly the case when using the pagination scheme
6
+ in combination with an infinite scrolling UI.
7
+
8
+ Geared Pagination allows you to define different ratios. By default, we will return 15 elements on page 1,
9
+ 30 on page 2, 50 on page 3, and 100 from page 4 and forward. This has proben to be a very sensible set of
10
+ ratios for much of the Basecamp UIs. But you can of course tweak the ratios, use fewer, or even none at all,
11
+ if you certain page calls for a fixed-rate scheme.
12
+
13
+ On json actions that set a page, we'll also also automatically set Link and X-Total-Count headers for APIs
14
+ to be able to page through a collection.
15
+
16
+ ## Example
17
+
18
+ ```ruby
19
+ class MessagesController < ApplicationController
20
+ def index
21
+ @page = current_page_from Message.order(created_at: :desc)
22
+ end
23
+ end
24
+
25
+ # app/views/messages/index.html.erb
26
+
27
+ Showing page <%= @page.number %> of <%= @page.collection.page_count %> (<%= @page.collection.record_count %> total messages):
28
+
29
+ <%= render @page.records %>
30
+
31
+ <% if @page.last? %>
32
+ No more pages!
33
+ <% else %>
34
+ <%= link_to "Next page", messages_path(page: @page.next_number) %>
35
+ <% end %>
36
+
37
+ ```
38
+
39
+ ## License
40
+ Geared Pagination is released under the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |test|
6
+ test.libs << "test"
7
+ test.test_files = FileList["test/*_test.rb"]
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'geared_pagination'
3
+ s.version = '0.1'
4
+ s.authors = 'David Heinemeier Hansson'
5
+ s.email = 'david@basecamp.com'
6
+ s.summary = 'Paginate Active Record sets at variable speeds'
7
+ s.homepage = 'https://github.com/basecamp/geared_pagination'
8
+ s.license = 'MIT'
9
+
10
+ s.required_ruby_version = '>= 1.9.3'
11
+
12
+ s.add_dependency 'activesupport', '>= 5.0'
13
+ s.add_dependency 'addressable', '>= 2.5.0'
14
+
15
+ s.add_development_dependency 'bundler', '~> 1.12'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- test/*`.split("\n")
19
+ end
@@ -0,0 +1,2 @@
1
+ require 'geared_pagination/recordset'
2
+ require 'geared_pagination/railtie' if defined?(Rails)
@@ -0,0 +1,30 @@
1
+ require 'geared_pagination/recordset'
2
+ require 'geared_pagination/headers'
3
+
4
+ module GearedPagination
5
+ module Controller
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ after_action :set_paginated_headers
10
+ end
11
+
12
+ private
13
+ def set_page_and_extract_portion_from(records, per_page: nil)
14
+ @page = current_page_from(records, per_page: per_page)
15
+ @page.records
16
+ end
17
+
18
+ def current_page_from(records, per_page: nil)
19
+ GearedPagination::Recordset.new(records, per_page: per_page).page(current_page_param)
20
+ end
21
+
22
+ def set_paginated_headers
23
+ GearedPagination::Headers.new(page: @page, controller: self).apply if @page.is_a?(GearedPagination::Page)
24
+ end
25
+
26
+ def current_page_param
27
+ params[:page].to_i > 0 ? params[:page].to_i : 1
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ require 'addressable/uri'
2
+
3
+ module GearedPagination
4
+ class Headers
5
+ def initialize(page:, controller:)
6
+ @page, @controller = page, controller
7
+ end
8
+
9
+ def apply
10
+ @controller.headers.update(headers) if applicable?
11
+ end
12
+
13
+ private
14
+ def headers
15
+ Hash.new.tap do |h|
16
+ h["X-Total-Count"] = @page.collection.records_count.to_s
17
+ h["Link"] = next_page_link_header unless @page.last?
18
+ end
19
+ end
20
+
21
+ def applicable?
22
+ @controller.request.format&.json?
23
+ end
24
+
25
+ def next_page_link_header
26
+ link_header(rel: :next, page_number: @page.next_number).to_s
27
+ end
28
+
29
+ def link_header(rel:, page_number:)
30
+ uri = Addressable::URI.parse(@controller.request.url)
31
+ uri.query_values = (uri.query_values || {}).merge("page" => page_number)
32
+ %{<#{uri}>; rel="#{rel}"}
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ require 'geared_pagination/portion'
2
+
3
+ module GearedPagination
4
+ class Page
5
+ attr_reader :number, :collection
6
+
7
+ def initialize(number, from:)
8
+ @number, @collection = number, from
9
+ @portion = GearedPagination::Portion.new(page_number: number, per_page: from.ratios)
10
+ end
11
+
12
+ def records
13
+ @records ||= @portion.from(collection.records)
14
+ end
15
+
16
+
17
+ def used?
18
+ records.load.any?
19
+ end
20
+
21
+ def empty?
22
+ records.load.none?
23
+ end
24
+
25
+
26
+ def first?
27
+ number == 1
28
+ end
29
+
30
+ def only?
31
+ collection.page_count == 1
32
+ end
33
+
34
+ def last?
35
+ number == collection.page_count
36
+ end
37
+
38
+
39
+ def next_number
40
+ number + 1
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ require 'geared_pagination/ratios'
2
+
3
+ module GearedPagination
4
+ class Portion
5
+ attr_reader :page_number, :ratios
6
+
7
+ def initialize(page_number: 1, per_page: GearedPagination::Ratios.new)
8
+ @page_number, @ratios = page_number, per_page
9
+ end
10
+
11
+ def from(scope)
12
+ scope.limit(limit).offset(offset)
13
+ end
14
+
15
+ def limit
16
+ ratios[page_number]
17
+ end
18
+
19
+ def offset
20
+ (page_number - 1).times.sum { |index| ratios[index + 1] }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ require 'rails/railtie'
2
+ require 'geared_pagination/controller'
3
+
4
+ class GearedPagination::Engine < ::Rails::Engine
5
+ initializer :webpacker do |app|
6
+ ActiveSupport.on_load :action_controller do
7
+ ActionController::Base.send :include, GearedPagination::Controller
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module GearedPagination
2
+ class Ratios
3
+ DEFAULTS = [ 15, 30, 50, 100 ]
4
+
5
+ def initialize(ratios = nil)
6
+ @ratios = Array(ratios || DEFAULTS)
7
+ end
8
+
9
+ def [](page_number)
10
+ @ratios[page_number - 1] || @ratios.last
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ require 'geared_pagination/ratios'
2
+ require 'geared_pagination/page'
3
+
4
+ module GearedPagination
5
+ class Recordset
6
+ attr_reader :records, :ratios
7
+
8
+ def initialize(records, per_page: nil)
9
+ @records = records
10
+ @ratios = GearedPagination::Ratios.new(per_page)
11
+ end
12
+
13
+ def page(number)
14
+ GearedPagination::Page.new(number, from: self)
15
+ end
16
+
17
+ def page_count
18
+ @page_count ||= begin
19
+ count = 0
20
+ residual = records_count
21
+
22
+ while residual > 0
23
+ count += 1
24
+ residual = residual - ratios[count]
25
+ end
26
+
27
+ count
28
+ end
29
+ end
30
+
31
+ def records_count
32
+ @records_count ||= records.unscope(:limit).unscope(:offset).count
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+ require 'active_support/core_ext/string/inquiry'
3
+ require 'geared_pagination/headers'
4
+ require 'geared_pagination/recordset'
5
+
6
+ class GearedPagination::HeadersTest < ActiveSupport::TestCase
7
+ Request = Struct.new(:url, :format)
8
+ Controller = Struct.new(:request, :headers)
9
+
10
+ setup do
11
+ @controller_serving_json = Controller.new(Request.new("http://example.com/collection.json", "json".inquiry), {})
12
+ @controller_serving_html = Controller.new(Request.new("http://example.com/collection", "html".inquiry), {})
13
+
14
+ @single_page_collection = GearedPagination::Recordset.new(Recording.all, per_page: 1000)
15
+ @many_page_collection = GearedPagination::Recordset.new(Recording.all, per_page: 1)
16
+ end
17
+
18
+ test "total count" do
19
+ GearedPagination::Headers.new(page: @many_page_collection.page(1), controller: @controller_serving_json).apply
20
+ assert_equal Recording.all.count.to_s, @controller_serving_json.headers["X-Total-Count"]
21
+ end
22
+
23
+ test "no link for html requests" do
24
+ GearedPagination::Headers.new(page: @many_page_collection.page(1), controller: @controller_serving_html).apply
25
+ assert @controller_serving_html.headers["Link"].nil?
26
+ end
27
+
28
+ test "no link for json request with single page" do
29
+ GearedPagination::Headers.new(page: @many_page_collection.page(1), controller: @controller_serving_html).apply
30
+ assert @controller_serving_html.headers["Link"].nil?
31
+ end
32
+
33
+ test "links for json request with multiple pages" do
34
+ GearedPagination::Headers.new(page: @many_page_collection.page(1), controller: @controller_serving_json).apply
35
+ assert_equal '<http://example.com/collection.json?page=2>; rel="next"',
36
+ @controller_serving_json.headers["Link"]
37
+
38
+ GearedPagination::Headers.new(page: @many_page_collection.page(2), controller: @controller_serving_json).apply
39
+ assert_equal '<http://example.com/collection.json?page=3>; rel="next"',
40
+ @controller_serving_json.headers["Link"]
41
+ end
42
+
43
+ test "no link for json request with multiple pages on last page" do
44
+ GearedPagination::Headers.new(page: @many_page_collection.page(Recording.all.count), controller: @controller_serving_json).apply
45
+ assert @controller_serving_json.headers["Link"].nil?
46
+ end
47
+ end
data/test/page_test.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+ require 'geared_pagination/recordset'
3
+
4
+ class GearedPagination::PageTest < ActiveSupport::TestCase
5
+ test "first" do
6
+ assert GearedPagination::Recordset.new(Recording.all).page(1).first?
7
+ assert_not GearedPagination::Recordset.new(Recording.all).page(2).first?
8
+ end
9
+
10
+ test "only" do
11
+ assert GearedPagination::Recordset.new(Recording.all, per_page: 1000).page(1).only?
12
+ assert_not GearedPagination::Recordset.new(Recording.all, per_page: 1).page(1).only?
13
+ end
14
+
15
+ test "last" do
16
+ assert GearedPagination::Recordset.new(Recording.all, per_page: 1000).page(1).last?
17
+ assert_not GearedPagination::Recordset.new(Recording.all, per_page: 1).page(1).last?
18
+ end
19
+
20
+ test "next_number" do
21
+ assert_equal 2, GearedPagination::Recordset.new(Recording.all, per_page: 1000).page(1).next_number
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+ require 'active_support/core_ext/array/access'
3
+ require 'geared_pagination/portion'
4
+
5
+ class GearedPagination::PortionTest < ActiveSupport::TestCase
6
+ test "offset" do
7
+ assert_equal 0, GearedPagination::Portion.new(page_number: 1).offset
8
+ assert_equal GearedPagination::Ratios::DEFAULTS.first, GearedPagination::Portion.new(page_number: 2).offset
9
+ assert_equal GearedPagination::Ratios::DEFAULTS.first + GearedPagination::Ratios::DEFAULTS.second, GearedPagination::Portion.new(page_number: 3).offset
10
+ end
11
+
12
+ test "limit" do
13
+ assert_equal GearedPagination::Ratios::DEFAULTS.first, GearedPagination::Portion.new(page_number: 1).limit
14
+ assert_equal GearedPagination::Ratios::DEFAULTS.second, GearedPagination::Portion.new(page_number: 2).limit
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+ require 'geared_pagination/ratios'
3
+
4
+ class GearedPagination::RatiosTest < ActiveSupport::TestCase
5
+ test "single limit" do
6
+ limits = GearedPagination::Ratios.new(10)
7
+
8
+ assert_equal 10, limits[1]
9
+ assert_equal 10, limits[2]
10
+ end
11
+
12
+ test "range of limits" do
13
+ limits = GearedPagination::Ratios.new([ 10, 25, 100 ])
14
+
15
+ assert_equal 10, limits[1]
16
+ assert_equal 25, limits[2]
17
+ assert_equal 100, limits[3]
18
+ assert_equal 100, limits[4]
19
+ end
20
+
21
+ test "default limits" do
22
+ limits = GearedPagination::Ratios.new
23
+
24
+ assert_equal GearedPagination::Ratios::DEFAULTS.first, limits[1]
25
+ assert_equal GearedPagination::Ratios::DEFAULTS.last, limits[99]
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ class Recordings
2
+ def initialize(recordings)
3
+ @recordings = recordings
4
+ @offset = 0
5
+ @limit = nil
6
+ end
7
+
8
+ def limit(size)
9
+ @limit = size
10
+ self
11
+ end
12
+
13
+ def offset(number)
14
+ @offset = number
15
+ self
16
+ end
17
+
18
+ def unscope(restriction)
19
+ case restriction
20
+ when :offset then @offset = 0
21
+ when :limit then @limit = nil
22
+ end
23
+
24
+ self
25
+ end
26
+
27
+
28
+ def [](index)
29
+ slice[index]
30
+ end
31
+
32
+ def count
33
+ slice.size
34
+ end
35
+ alias :size :count
36
+
37
+ def include?(record)
38
+ slice.include?(record)
39
+ end
40
+
41
+
42
+ private
43
+ def slice
44
+ @recordings[@offset..(@limit ? @offset + (@limit - 1) : -1)]
45
+ end
46
+ end
47
+
48
+ class Recording
49
+ def self.all(count = 120)
50
+ Recordings.new(count.times.collect { |i| new(i) })
51
+ end
52
+
53
+ attr_reader :number
54
+
55
+ def initialize(number)
56
+ @number = 1
57
+ end
58
+
59
+ def ==(comparison)
60
+ @number == comparison.number
61
+ end
62
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_helper'
2
+ require 'geared_pagination/recordset'
3
+
4
+ class GearedPagination::RecordsetTest < ActiveSupport::TestCase
5
+ test "single limit pagination" do
6
+ collection = GearedPagination::Recordset.new(Recording.all, per_page: 10)
7
+
8
+ assert_equal 10, collection.page(1).records.size
9
+ assert_equal 10, collection.page(2).records.size
10
+ end
11
+
12
+ test "variable limit pagination" do
13
+ collection = GearedPagination::Recordset.new(Recording.all, per_page: [ 10, 15, 20 ])
14
+
15
+ assert_equal 10, collection.page(1).records.size
16
+ assert collection.page(1).records.include?(Recording.all[0])
17
+
18
+ assert_equal 15, collection.page(2).records.size
19
+ assert collection.page(2).records.include?(Recording.all[11])
20
+
21
+ assert_equal 20, collection.page(3).records.size
22
+ assert collection.page(3).records.include?(Recording.all[26])
23
+
24
+ assert_equal 20, collection.page(4).records.size
25
+ assert collection.page(4).records.include?(Recording.all[46])
26
+ end
27
+
28
+ test "page count" do
29
+ assert_equal 7, GearedPagination::Recordset.new(Recording.all, per_page: [ 10, 15, 20 ]).page_count
30
+ end
31
+
32
+ test "records count" do
33
+ assert_equal Recording.all.count, GearedPagination::Recordset.new(Recording.all, per_page: [ 10, 15, 20 ]).records_count
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ require 'active_support'
3
+ require 'active_support/testing/autorun'
4
+ require 'byebug'
5
+ require 'recording_stubs'
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geared_pagination
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - David Heinemeier Hansson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: addressable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.5.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.5.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.12'
55
+ description:
56
+ email: david@basecamp.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - Gemfile
62
+ - Gemfile.lock
63
+ - MIT-LICENSE
64
+ - README.md
65
+ - Rakefile
66
+ - geared_pagination.gemspec
67
+ - lib/geared_pagination.rb
68
+ - lib/geared_pagination/controller.rb
69
+ - lib/geared_pagination/headers.rb
70
+ - lib/geared_pagination/page.rb
71
+ - lib/geared_pagination/portion.rb
72
+ - lib/geared_pagination/railtie.rb
73
+ - lib/geared_pagination/ratios.rb
74
+ - lib/geared_pagination/recordset.rb
75
+ - test/headers_test.rb
76
+ - test/page_test.rb
77
+ - test/portion_test.rb
78
+ - test/ratios_test.rb
79
+ - test/recording_stubs.rb
80
+ - test/recordset_test.rb
81
+ - test/test_helper.rb
82
+ homepage: https://github.com/basecamp/geared_pagination
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 1.9.3
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.6.8
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Paginate Active Record sets at variable speeds
106
+ test_files:
107
+ - test/headers_test.rb
108
+ - test/page_test.rb
109
+ - test/portion_test.rb
110
+ - test/ratios_test.rb
111
+ - test/recording_stubs.rb
112
+ - test/recordset_test.rb
113
+ - test/test_helper.rb