geared_pagination 0.1 → 1.1.0

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 (104) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +29 -0
  3. data/.gitignore +10 -0
  4. data/Gemfile +4 -1
  5. data/Gemfile.lock +128 -14
  6. data/README.md +76 -9
  7. data/Rakefile +1 -0
  8. data/bin/rails +16 -0
  9. data/geared_pagination.gemspec +3 -3
  10. data/lib/geared_pagination.rb +1 -1
  11. data/lib/geared_pagination/controller.rb +11 -6
  12. data/lib/geared_pagination/cursor.rb +40 -0
  13. data/lib/geared_pagination/{railtie.rb → engine.rb} +2 -2
  14. data/lib/geared_pagination/headers.rb +16 -9
  15. data/lib/geared_pagination/order.rb +44 -0
  16. data/lib/geared_pagination/page.rb +25 -10
  17. data/lib/geared_pagination/portions.rb +2 -0
  18. data/lib/geared_pagination/portions/portion_at_cursor.rb +134 -0
  19. data/lib/geared_pagination/portions/portion_at_offset.rb +35 -0
  20. data/lib/geared_pagination/ratios.rb +14 -2
  21. data/lib/geared_pagination/recordset.rb +26 -6
  22. data/test/controller_test.rb +47 -0
  23. data/test/cursor_test.rb +34 -0
  24. data/test/dummy/Rakefile +3 -0
  25. data/test/dummy/app/assets/config/manifest.js +3 -0
  26. data/test/dummy/app/assets/images/.keep +0 -0
  27. data/test/dummy/app/assets/javascripts/application.js +15 -0
  28. data/test/dummy/app/assets/javascripts/cable.js +13 -0
  29. data/test/dummy/app/assets/javascripts/channels/.keep +0 -0
  30. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  31. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  32. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  33. data/test/dummy/app/controllers/application_controller.rb +2 -0
  34. data/test/dummy/app/controllers/concerns/.keep +0 -0
  35. data/test/dummy/app/controllers/recordings_controller.rb +11 -0
  36. data/test/dummy/app/helpers/application_helper.rb +2 -0
  37. data/test/dummy/app/jobs/application_job.rb +2 -0
  38. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  39. data/test/dummy/app/models/application_record.rb +3 -0
  40. data/test/dummy/app/models/concerns/.keep +0 -0
  41. data/test/dummy/app/models/recording.rb +2 -0
  42. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  43. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  44. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  45. data/test/dummy/bin/rails +4 -0
  46. data/test/dummy/config.ru +5 -0
  47. data/test/dummy/config/application.rb +21 -0
  48. data/test/dummy/config/boot.rb +5 -0
  49. data/test/dummy/config/cable.yml +10 -0
  50. data/test/dummy/config/database.yml +25 -0
  51. data/test/dummy/config/environment.rb +5 -0
  52. data/test/dummy/config/environments/development.rb +61 -0
  53. data/test/dummy/config/environments/production.rb +94 -0
  54. data/test/dummy/config/environments/test.rb +46 -0
  55. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  56. data/test/dummy/config/initializers/assets.rb +14 -0
  57. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  58. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  59. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  60. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  61. data/test/dummy/config/initializers/inflections.rb +16 -0
  62. data/test/dummy/config/initializers/mime_types.rb +4 -0
  63. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  64. data/test/dummy/config/locales/en.yml +33 -0
  65. data/test/dummy/config/puma.rb +34 -0
  66. data/test/dummy/config/routes.rb +5 -0
  67. data/test/dummy/config/spring.rb +6 -0
  68. data/test/dummy/config/storage.yml +34 -0
  69. data/test/dummy/db/migrate/20200504213548_create_recordings.rb +7 -0
  70. data/test/dummy/db/schema.rb +19 -0
  71. data/test/dummy/db/seeds.rb +7 -0
  72. data/test/dummy/lib/assets/.keep +0 -0
  73. data/test/dummy/lib/tasks/.keep +0 -0
  74. data/test/dummy/log/.keep +0 -0
  75. data/test/dummy/public/404.html +67 -0
  76. data/test/dummy/public/422.html +67 -0
  77. data/test/dummy/public/500.html +66 -0
  78. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  79. data/test/dummy/public/apple-touch-icon.png +0 -0
  80. data/test/dummy/public/favicon.ico +0 -0
  81. data/test/dummy/public/robots.txt +1 -0
  82. data/test/dummy/test/application_system_test_case.rb +5 -0
  83. data/test/dummy/test/controllers/.keep +0 -0
  84. data/test/dummy/test/fixtures/.keep +0 -0
  85. data/test/dummy/test/fixtures/files/.keep +0 -0
  86. data/test/dummy/test/helpers/.keep +0 -0
  87. data/test/dummy/test/integration/.keep +0 -0
  88. data/test/dummy/test/mailers/.keep +0 -0
  89. data/test/dummy/test/models/.keep +0 -0
  90. data/test/dummy/test/system/.keep +0 -0
  91. data/test/dummy/test/test_helper.rb +10 -0
  92. data/test/dummy/vendor/.keep +0 -0
  93. data/test/headers_test.rb +14 -12
  94. data/test/order_test.rb +34 -0
  95. data/test/page_test.rb +45 -2
  96. data/test/portion_at_cursor_test.rb +84 -0
  97. data/test/portion_at_offset_test.rb +36 -0
  98. data/test/ratios_test.rb +4 -0
  99. data/test/recordset_test.rb +52 -14
  100. data/test/test_helper.rb +12 -4
  101. metadata +164 -14
  102. data/lib/geared_pagination/portion.rb +0 -23
  103. data/test/portion_test.rb +0 -16
  104. data/test/recording_stubs.rb +0 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 86fef30f9fb2e850e8b305a079d05d39007ec6bd
4
- data.tar.gz: 83e91624d0fbd65747604f91628e826f00a131b0
2
+ SHA256:
3
+ metadata.gz: 05c58b3498b3b341fd11f5500545278837414633b714f7c02aca2c3718c86227
4
+ data.tar.gz: b3c4729222ff73faf62566f48187ae522544a1491a6fba02ff4b921e24c1dcbd
5
5
  SHA512:
6
- metadata.gz: f7e00eb4a7598860c873eb1810ffdba1c8e5a30d625d42b6a7e10f035786e593724b74c8c496089495ada18c992af7b41534041e1b5951f8573932a3d533509e
7
- data.tar.gz: c2feb3068a763034a474fca15cd793b3fdeae695cee4c6f0187c4ea2918a76a24f9a124061407922a3b397137119fdc234413153563ba0ed9f3301af9204238e
6
+ metadata.gz: c327c83175f3a27a8a91732ab3ae81c3c5d0f0da3ef54726a0d30042e7bb90b4ba933781ccee5b57cc7808c477e9bb12b5e67204e0c2417450a82350eeeadacf
7
+ data.tar.gz: ed877d95bad4a4d4083ee05f02d7211aa9e28c96eaf948cd387baa7b930373d5e08227024268bd1efef46c8fa75f84ac71c297a9080626c9c39ce8148e6e1e1c
@@ -0,0 +1,29 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ tests:
5
+ runs-on: ubuntu-latest
6
+
7
+ steps:
8
+ - uses: actions/checkout@v1
9
+
10
+ - name: Set up Ruby 2.6
11
+ uses: actions/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.6.x
14
+
15
+ - name: Cache gem dependencies
16
+ uses: actions/cache@v1
17
+ with:
18
+ path: vendor/bundle
19
+ key: ${{ runner.os }}-bundler-${{ hashFiles('**/Gemfile.lock') }}
20
+ restore-keys: ${{ runner.os }}-bundler-
21
+
22
+ - name: Install system dependencies
23
+ run: sudo apt-get update && sudo apt-get install libsqlite3-dev
24
+
25
+ - name: Install gem dependencies
26
+ run: gem install bundler && bundle install --jobs 4 --retry 3 --path vendor/bundle
27
+
28
+ - name: Run tests
29
+ run: bundle exec rake test
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ .byebug_history
3
+
4
+ test/dummy/db/*.sqlite3
5
+ test/dummy/db/*.sqlite3-journal
6
+ test/dummy/log/*.log
7
+ test/dummy/node_modules/
8
+ test/dummy/yarn-error.log
9
+ test/dummy/storage/
10
+ test/dummy/tmp/
data/Gemfile CHANGED
@@ -3,4 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  gem 'rake'
6
- gem 'byebug'
6
+ gem 'byebug'
7
+
8
+ gem 'rails', '>= 5.0'
9
+ gem 'sqlite3'
@@ -1,29 +1,141 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- geared_pagination (0.1)
4
+ geared_pagination (1.1.0)
5
5
  activesupport (>= 5.0)
6
6
  addressable (>= 2.5.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activesupport (5.1.0.beta1)
11
+ actioncable (6.0.3)
12
+ actionpack (= 6.0.3)
13
+ nio4r (~> 2.0)
14
+ websocket-driver (>= 0.6.1)
15
+ actionmailbox (6.0.3)
16
+ actionpack (= 6.0.3)
17
+ activejob (= 6.0.3)
18
+ activerecord (= 6.0.3)
19
+ activestorage (= 6.0.3)
20
+ activesupport (= 6.0.3)
21
+ mail (>= 2.7.1)
22
+ actionmailer (6.0.3)
23
+ actionpack (= 6.0.3)
24
+ actionview (= 6.0.3)
25
+ activejob (= 6.0.3)
26
+ mail (~> 2.5, >= 2.5.4)
27
+ rails-dom-testing (~> 2.0)
28
+ actionpack (6.0.3)
29
+ actionview (= 6.0.3)
30
+ activesupport (= 6.0.3)
31
+ rack (~> 2.0, >= 2.0.8)
32
+ rack-test (>= 0.6.3)
33
+ rails-dom-testing (~> 2.0)
34
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
35
+ actiontext (6.0.3)
36
+ actionpack (= 6.0.3)
37
+ activerecord (= 6.0.3)
38
+ activestorage (= 6.0.3)
39
+ activesupport (= 6.0.3)
40
+ nokogiri (>= 1.8.5)
41
+ actionview (6.0.3)
42
+ activesupport (= 6.0.3)
43
+ builder (~> 3.1)
44
+ erubi (~> 1.4)
45
+ rails-dom-testing (~> 2.0)
46
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
47
+ activejob (6.0.3)
48
+ activesupport (= 6.0.3)
49
+ globalid (>= 0.3.6)
50
+ activemodel (6.0.3)
51
+ activesupport (= 6.0.3)
52
+ activerecord (6.0.3)
53
+ activemodel (= 6.0.3)
54
+ activesupport (= 6.0.3)
55
+ activestorage (6.0.3)
56
+ actionpack (= 6.0.3)
57
+ activejob (= 6.0.3)
58
+ activerecord (= 6.0.3)
59
+ marcel (~> 0.3.1)
60
+ activesupport (6.0.3)
12
61
  concurrent-ruby (~> 1.0, >= 1.0.2)
13
- i18n (~> 0.7)
62
+ i18n (>= 0.7, < 2)
14
63
  minitest (~> 5.1)
15
64
  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)
65
+ zeitwerk (~> 2.2, >= 2.2.2)
66
+ addressable (2.7.0)
67
+ public_suffix (>= 2.0.2, < 5.0)
68
+ builder (3.2.4)
69
+ byebug (10.0.2)
70
+ concurrent-ruby (1.1.6)
71
+ crass (1.0.6)
72
+ erubi (1.9.0)
73
+ globalid (0.4.2)
74
+ activesupport (>= 4.2.0)
75
+ i18n (1.8.2)
76
+ concurrent-ruby (~> 1.0)
77
+ loofah (2.5.0)
78
+ crass (~> 1.0.2)
79
+ nokogiri (>= 1.5.9)
80
+ mail (2.7.1)
81
+ mini_mime (>= 0.1.1)
82
+ marcel (0.3.3)
83
+ mimemagic (~> 0.3.2)
84
+ method_source (1.0.0)
85
+ mimemagic (0.3.5)
86
+ mini_mime (1.0.2)
87
+ mini_portile2 (2.4.0)
88
+ minitest (5.14.0)
89
+ nio4r (2.5.2)
90
+ nokogiri (1.10.9)
91
+ mini_portile2 (~> 2.4.0)
92
+ public_suffix (4.0.6)
93
+ rack (2.2.2)
94
+ rack-test (1.1.0)
95
+ rack (>= 1.0, < 3)
96
+ rails (6.0.3)
97
+ actioncable (= 6.0.3)
98
+ actionmailbox (= 6.0.3)
99
+ actionmailer (= 6.0.3)
100
+ actionpack (= 6.0.3)
101
+ actiontext (= 6.0.3)
102
+ actionview (= 6.0.3)
103
+ activejob (= 6.0.3)
104
+ activemodel (= 6.0.3)
105
+ activerecord (= 6.0.3)
106
+ activestorage (= 6.0.3)
107
+ activesupport (= 6.0.3)
108
+ bundler (>= 1.3.0)
109
+ railties (= 6.0.3)
110
+ sprockets-rails (>= 2.0.0)
111
+ rails-dom-testing (2.0.3)
112
+ activesupport (>= 4.2.0)
113
+ nokogiri (>= 1.6)
114
+ rails-html-sanitizer (1.3.0)
115
+ loofah (~> 2.3)
116
+ railties (6.0.3)
117
+ actionpack (= 6.0.3)
118
+ activesupport (= 6.0.3)
119
+ method_source
120
+ rake (>= 0.8.7)
121
+ thor (>= 0.20.3, < 2.0)
122
+ rake (13.0.1)
123
+ sprockets (4.0.0)
124
+ concurrent-ruby (~> 1.0)
125
+ rack (> 1, < 3)
126
+ sprockets-rails (3.2.1)
127
+ actionpack (>= 4.0)
128
+ activesupport (>= 4.0)
129
+ sprockets (>= 3.0.0)
130
+ sqlite3 (1.4.2)
131
+ thor (1.0.1)
132
+ thread_safe (0.3.6)
133
+ tzinfo (1.2.7)
26
134
  thread_safe (~> 0.1)
135
+ websocket-driver (0.7.1)
136
+ websocket-extensions (>= 0.1.0)
137
+ websocket-extensions (0.1.4)
138
+ zeitwerk (2.3.0)
27
139
 
28
140
  PLATFORMS
29
141
  ruby
@@ -32,7 +144,9 @@ DEPENDENCIES
32
144
  bundler (~> 1.12)
33
145
  byebug
34
146
  geared_pagination!
147
+ rails (>= 5.0)
35
148
  rake
149
+ sqlite3
36
150
 
37
151
  BUNDLED WITH
38
- 1.14.6
152
+ 1.17.2
data/README.md CHANGED
@@ -1,40 +1,107 @@
1
1
  # Geared Pagination
2
2
 
3
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
4
+ frequently not the most sensible way to page through a large recordset when you care about serving the
5
5
  initial request as quickly as possible. This is particularly the case when using the pagination scheme
6
6
  in combination with an infinite scrolling UI.
7
7
 
8
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
9
+ 30 on page 2, 50 on page 3, and 100 from page 4 and forward. This has proven to be a very sensible set of
10
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.
11
+ if a certain page calls for a fixed-rate scheme.
12
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.
13
+ On JSON actions that set a page, we'll also automatically set Link and X-Total-Count headers for APIs
14
+ to be able to page through a recordset.
15
15
 
16
16
  ## Example
17
17
 
18
18
  ```ruby
19
19
  class MessagesController < ApplicationController
20
20
  def index
21
- @page = current_page_from Message.order(created_at: :desc)
21
+ set_page_and_extract_portion_from Message.order(created_at: :desc)
22
22
  end
23
23
  end
24
24
 
25
25
  # app/views/messages/index.html.erb
26
26
 
27
- Showing page <%= @page.number %> of <%= @page.collection.page_count %> (<%= @page.collection.record_count %> total messages):
27
+ Showing page <%= @page.number %> of <%= @page.recordset.page_count %> (<%= @page.recordset.records_count %> total messages):
28
28
 
29
29
  <%= render @page.records %>
30
30
 
31
31
  <% if @page.last? %>
32
32
  No more pages!
33
- <% else %>
34
- <%= link_to "Next page", messages_path(page: @page.next_number) %>
33
+ <% else %>
34
+ <%= link_to "Next page", messages_path(page: @page.next_param) %>
35
35
  <% end %>
36
36
 
37
37
  ```
38
38
 
39
+ ## Cursor-based pagination
40
+
41
+ By default, Geared Pagination uses *offset-based pagination*: the `page` query parameter contains the page number. Each page’s records are located using a query with an `OFFSET` clause, like so:
42
+
43
+ ```sql
44
+ SELECT *
45
+ FROM messages
46
+ ORDER BY created_at DESC
47
+ LIMIT 30
48
+ OFFSET 15
49
+ ```
50
+
51
+ You may prefer to use *cursor-based pagination* instead. In cursor-based pagination, the `page` parameter contains a “cursor” describing the last row of the previous page. Each page’s records are located using a query with conditions that only match records after the previous page. For example, if the last record on the previous page had a `created_at` value of `2019-01-24T12:35:26.381Z` and an ID of `7354857`, the current page’s records would be found with a query like this one:
52
+
53
+ ```sql
54
+ SELECT *
55
+ FROM messages
56
+ WHERE (created_at = '2019-01-24T12:35:26.381Z' AND id < 7354857)
57
+ OR created_at < '2019-01-24T12:35:26.381Z'
58
+ ORDER BY created_at DESC, id DESC
59
+ LIMIT 30
60
+ ```
61
+
62
+ Geared Pagination supports cursor-based pagination. To use it, pass the `:ordered_by` option to `set_page_and_extract_portion_from` in your controllers. Provide the orders to apply to the paginated relation:
63
+
64
+ ```ruby
65
+ set_page_and_extract_portion_from Message.all, ordered_by: { created_at: :desc, id: :desc }
66
+ ```
67
+
68
+ Geared Pagination uses the ordered attributes (in the above example, `created_at` and `id`) to generate cursors:
69
+
70
+ ```erb
71
+ <%= link_to "Next page", messages_path(page: @page.next_param) %>
72
+ <!-- <a href="/messages?page=eyJwYWdlX251...">Next page</a> -->
73
+ ```
74
+
75
+ Cursors encode the information Geared Pagination needs to query for the corresponding page’s records: the page number for choosing a page size, and the values of each of the ordered attributes (`created_at` and `id`).
76
+
77
+ ### When should I use cursor-based pagination?
78
+
79
+ Cursor-based pagination can outperform offset-based pagination when paginating deeply into a large number of records. DBs commonly execute queries with `OFFSET` clauses by counting past `OFFSET` records one at a time, so each page in offset-based pagination takes slightly longer to load than the last. With cursor-based pagination and an appropriate index, the DB can jump directly to the beginning of each page without scanning.
80
+
81
+ The tradeoff is that Geared Pagination only supports cursor-based pagination on simple relations with simple, column-only orders. Cursor-based pagination also won’t perform better than offset-based pagination without an ordered index. Stick with offset-based pagination if:
82
+ * You need complex ordering on a complex relation
83
+ * You’re paginating a small and/or bounded number of records
84
+
85
+ ## Caching
86
+
87
+ To account for the current page in fragment caches, include the `@page` directly.
88
+ That includes the current page number and gear ratios.
89
+
90
+ Fragment caching a message's comments:
91
+ ```ruby
92
+ <% cache [ @message, @page ] do %>
93
+ <%= render @page.records %>
94
+ <% end %>
95
+ ```
96
+
97
+ NOTE: The page does not include cache keys for all the records. That would require loading all the records,
98
+ defeating the purpose of using the cache. Use a parent record, like a message that's touched when
99
+ new comments are posted, as the cache key instead.
100
+
101
+ ## ETags
102
+
103
+ When a controller action sets an ETag and uses geared pagination, the current page and gear ratios are
104
+ automatically included in the ETag.
105
+
39
106
  ## License
40
107
  Geared Pagination is released under the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -5,6 +5,7 @@ require "rake/testtask"
5
5
  Rake::TestTask.new do |test|
6
6
  test.libs << "test"
7
7
  test.test_files = FileList["test/*_test.rb"]
8
+ test.warning = false
8
9
  end
9
10
 
10
11
  task default: :test
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('..', __dir__)
6
+ ENGINE_PATH = File.expand_path('../lib/geared_pagination/engine', __dir__)
7
+ APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
8
+
9
+ # Set up gems listed in the Gemfile.
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
11
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
12
+
13
+ require 'rails'
14
+ require 'action_controller/railtie'
15
+ require 'rails/test_unit/railtie'
16
+ require 'rails/engine/commands'
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'geared_pagination'
3
- s.version = '0.1'
3
+ s.version = '1.1.0'
4
4
  s.authors = 'David Heinemeier Hansson'
5
5
  s.email = 'david@basecamp.com'
6
6
  s.summary = 'Paginate Active Record sets at variable speeds'
@@ -14,6 +14,6 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.add_development_dependency 'bundler', '~> 1.12'
16
16
 
17
- s.files = `git ls-files`.split("\n")
18
- s.test_files = `git ls-files -- test/*`.split("\n")
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- test/*`.split("\n")
19
19
  end
@@ -1,2 +1,2 @@
1
1
  require 'geared_pagination/recordset'
2
- require 'geared_pagination/railtie' if defined?(Rails)
2
+ require 'geared_pagination/engine' if defined?(Rails)
@@ -7,24 +7,29 @@ module GearedPagination
7
7
 
8
8
  included do
9
9
  after_action :set_paginated_headers
10
+ etag { @page if geared_page? }
10
11
  end
11
12
 
12
13
  private
13
- def set_page_and_extract_portion_from(records, per_page: nil)
14
- @page = current_page_from(records, per_page: per_page)
14
+ def set_page_and_extract_portion_from(records, ordered_by: nil, per_page: nil)
15
+ @page = current_page_from(records, ordered_by: ordered_by, per_page: per_page)
15
16
  @page.records
16
17
  end
17
18
 
18
- def current_page_from(records, per_page: nil)
19
- GearedPagination::Recordset.new(records, per_page: per_page).page(current_page_param)
19
+ def current_page_from(records, ordered_by: nil, per_page: nil)
20
+ GearedPagination::Recordset.new(records, ordered_by: ordered_by, per_page: per_page).page(current_page_param)
20
21
  end
21
22
 
22
23
  def set_paginated_headers
23
- GearedPagination::Headers.new(page: @page, controller: self).apply if @page.is_a?(GearedPagination::Page)
24
+ GearedPagination::Headers.new(page: @page, controller: self).apply if geared_page?
25
+ end
26
+
27
+ def geared_page?
28
+ @page.is_a? GearedPagination::Page
24
29
  end
25
30
 
26
31
  def current_page_param
27
- params[:page].to_i > 0 ? params[:page].to_i : 1
32
+ params[:page]
28
33
  end
29
34
  end
30
35
  end
@@ -0,0 +1,40 @@
1
+ require 'base64'
2
+ require 'active_support/json'
3
+
4
+ module GearedPagination
5
+ class Cursor
6
+ class << self
7
+ def from_param(key)
8
+ key.present? ? decode(key) : new
9
+ end
10
+
11
+ def decode(key)
12
+ if attributes = ActiveSupport::JSON.decode(Base64.urlsafe_decode64(key))
13
+ new **attributes.deep_symbolize_keys
14
+ end
15
+ end
16
+
17
+ def encode(page_number: 1, values: {})
18
+ Base64.urlsafe_encode64 ActiveSupport::JSON.encode(page_number: page_number, values: values)
19
+ end
20
+ end
21
+
22
+ attr_reader :values
23
+
24
+ def initialize(page_number: 1, values: {})
25
+ @page_number, @values = page_number, values
26
+ end
27
+
28
+ def page_number
29
+ @page_number > 0 ? @page_number : 1
30
+ end
31
+
32
+ def fetch(attribute)
33
+ values.fetch(attribute)
34
+ end
35
+
36
+ def include?(attribute)
37
+ values.include?(attribute)
38
+ end
39
+ end
40
+ end