geared_pagination 0.2 → 1.0.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 (101) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +29 -0
  3. data/.gitignore +9 -0
  4. data/Gemfile +3 -1
  5. data/Gemfile.lock +123 -38
  6. data/README.md +47 -2
  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 +5 -5
  12. data/lib/geared_pagination/cursor.rb +40 -0
  13. data/lib/geared_pagination/{railtie.rb → engine.rb} +1 -1
  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 +13 -7
  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/{portion.rb → portions/portion_at_offset.rb} +6 -2
  20. data/lib/geared_pagination/recordset.rb +25 -5
  21. data/test/controller_test.rb +2 -24
  22. data/test/cursor_test.rb +34 -0
  23. data/test/dummy/Rakefile +3 -0
  24. data/test/dummy/app/assets/config/manifest.js +3 -0
  25. data/test/dummy/app/assets/images/.keep +0 -0
  26. data/test/dummy/app/assets/javascripts/application.js +15 -0
  27. data/test/dummy/app/assets/javascripts/cable.js +13 -0
  28. data/test/dummy/app/assets/javascripts/channels/.keep +0 -0
  29. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  30. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  31. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  32. data/test/dummy/app/controllers/application_controller.rb +2 -0
  33. data/test/dummy/app/controllers/concerns/.keep +0 -0
  34. data/test/dummy/app/controllers/recordings_controller.rb +11 -0
  35. data/test/dummy/app/helpers/application_helper.rb +2 -0
  36. data/test/dummy/app/jobs/application_job.rb +2 -0
  37. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  38. data/test/dummy/app/models/application_record.rb +3 -0
  39. data/test/dummy/app/models/concerns/.keep +0 -0
  40. data/test/dummy/app/models/recording.rb +2 -0
  41. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  42. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  43. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  44. data/test/dummy/bin/rails +4 -0
  45. data/test/dummy/config.ru +5 -0
  46. data/test/dummy/config/application.rb +21 -0
  47. data/test/dummy/config/boot.rb +5 -0
  48. data/test/dummy/config/cable.yml +10 -0
  49. data/test/dummy/config/database.yml +25 -0
  50. data/test/dummy/config/environment.rb +5 -0
  51. data/test/dummy/config/environments/development.rb +61 -0
  52. data/test/dummy/config/environments/production.rb +94 -0
  53. data/test/dummy/config/environments/test.rb +46 -0
  54. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  55. data/test/dummy/config/initializers/assets.rb +14 -0
  56. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  58. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  59. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  60. data/test/dummy/config/initializers/inflections.rb +16 -0
  61. data/test/dummy/config/initializers/mime_types.rb +4 -0
  62. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  63. data/test/dummy/config/locales/en.yml +33 -0
  64. data/test/dummy/config/puma.rb +34 -0
  65. data/test/dummy/config/routes.rb +5 -0
  66. data/test/dummy/config/spring.rb +6 -0
  67. data/test/dummy/config/storage.yml +34 -0
  68. data/test/dummy/db/migrate/20200504213548_create_recordings.rb +7 -0
  69. data/test/dummy/db/schema.rb +19 -0
  70. data/test/dummy/db/seeds.rb +7 -0
  71. data/test/dummy/lib/assets/.keep +0 -0
  72. data/test/dummy/lib/tasks/.keep +0 -0
  73. data/test/dummy/log/.keep +0 -0
  74. data/test/dummy/public/404.html +67 -0
  75. data/test/dummy/public/422.html +67 -0
  76. data/test/dummy/public/500.html +66 -0
  77. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  78. data/test/dummy/public/apple-touch-icon.png +0 -0
  79. data/test/dummy/public/favicon.ico +0 -0
  80. data/test/dummy/public/robots.txt +1 -0
  81. data/test/dummy/test/application_system_test_case.rb +5 -0
  82. data/test/dummy/test/controllers/.keep +0 -0
  83. data/test/dummy/test/fixtures/.keep +0 -0
  84. data/test/dummy/test/fixtures/files/.keep +0 -0
  85. data/test/dummy/test/helpers/.keep +0 -0
  86. data/test/dummy/test/integration/.keep +0 -0
  87. data/test/dummy/test/mailers/.keep +0 -0
  88. data/test/dummy/test/models/.keep +0 -0
  89. data/test/dummy/test/system/.keep +0 -0
  90. data/test/dummy/test/test_helper.rb +10 -0
  91. data/test/dummy/vendor/.keep +0 -0
  92. data/test/headers_test.rb +5 -3
  93. data/test/order_test.rb +34 -0
  94. data/test/page_test.rb +17 -3
  95. data/test/portion_at_cursor_test.rb +84 -0
  96. data/test/portion_at_offset_test.rb +33 -0
  97. data/test/recordset_test.rb +41 -3
  98. data/test/test_helper.rb +12 -4
  99. metadata +158 -10
  100. data/test/portion_test.rb +0 -27
  101. data/test/recording_stubs.rb +0 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f2ef2f3f5dc638b5cd4b6ab2459cbf4e513b85e4
4
- data.tar.gz: 3ae73c3c52ee2dd1a257daff921cabe401373733
2
+ SHA256:
3
+ metadata.gz: 6442a3bf0d55e8a7959814c6fd7d7e02da254952dbd829493aaf99c01bdd85f4
4
+ data.tar.gz: d064b59363360f871c6cc91adcce01d4a863d6379282fabb354fbeea0f402cfb
5
5
  SHA512:
6
- metadata.gz: a66259e261f8f9b98459cbd754039fbfb304fd8c700181b3babb4dca4ceeb83efb2e49dbf1e00d1238ede83f1ddbc9d96812cdf603c5e0644bd2d59969fab21a
7
- data.tar.gz: ba3b678d00e7615b1c8595ca342bd2d9cd1f4dee75a2e20f5ab93f178caf19b25b6d2abd6af3a4eb26b8dcf798ecee69a21d231fd4637e22a33f3fe9ce1087aa
6
+ metadata.gz: becae20eab1dd38355befb8a1440dcd432c0743f94d59f27f7323a7a2386849736605c96ddb344aa45d039569d4a5b406d0732fec2576cdbe093a602084e4fbc
7
+ data.tar.gz: 066f7da6d2db1875602aeb1517bf0cb19be41fc142dd3104cd68e0e204b77c3f196cac3ca19604ef75132424a7c6d988352c2e5d5ee1ed651047b4680d5c0c6c
@@ -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
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .byebug_history
2
+
3
+ test/dummy/db/*.sqlite3
4
+ test/dummy/db/*.sqlite3-journal
5
+ test/dummy/log/*.log
6
+ test/dummy/node_modules/
7
+ test/dummy/yarn-error.log
8
+ test/dummy/storage/
9
+ test/dummy/tmp/
data/Gemfile CHANGED
@@ -4,4 +4,6 @@ gemspec
4
4
 
5
5
  gem 'rake'
6
6
  gem 'byebug'
7
- gem 'actionpack', '>= 5'
7
+
8
+ gem 'rails', '>= 5.0'
9
+ gem 'sqlite3'
data/Gemfile.lock CHANGED
@@ -1,67 +1,152 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- geared_pagination (0.1)
4
+ geared_pagination (0.2)
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
- actionpack (5.0.2)
12
- actionview (= 5.0.2)
13
- activesupport (= 5.0.2)
14
- rack (~> 2.0)
15
- rack-test (~> 0.6.3)
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)
16
27
  rails-dom-testing (~> 2.0)
17
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
18
- actionview (5.0.2)
19
- activesupport (= 5.0.2)
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)
20
43
  builder (~> 3.1)
21
- erubis (~> 2.7.0)
44
+ erubi (~> 1.4)
22
45
  rails-dom-testing (~> 2.0)
23
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
24
- activesupport (5.0.2)
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)
25
61
  concurrent-ruby (~> 1.0, >= 1.0.2)
26
- i18n (~> 0.7)
62
+ i18n (>= 0.7, < 2)
27
63
  minitest (~> 5.1)
28
64
  tzinfo (~> 1.1)
29
- addressable (2.5.1)
30
- public_suffix (~> 2.0, >= 2.0.2)
31
- builder (3.2.3)
32
- byebug (9.0.6)
33
- concurrent-ruby (1.0.5)
34
- erubis (2.7.0)
35
- i18n (0.8.1)
36
- loofah (2.0.3)
65
+ zeitwerk (~> 2.2, >= 2.2.2)
66
+ addressable (2.5.2)
67
+ public_suffix (>= 2.0.2, < 4.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)
37
79
  nokogiri (>= 1.5.9)
38
- mini_portile2 (2.1.0)
39
- minitest (5.10.1)
40
- nokogiri (1.7.1)
41
- mini_portile2 (~> 2.1.0)
42
- public_suffix (2.0.5)
43
- rack (2.0.1)
44
- rack-test (0.6.3)
45
- rack (>= 1.0)
46
- rails-dom-testing (2.0.2)
47
- activesupport (>= 4.2.0, < 6.0)
48
- nokogiri (~> 1.6)
49
- rails-html-sanitizer (1.0.3)
50
- loofah (~> 2.0)
51
- rake (12.0.0)
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 (3.0.3)
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)
52
132
  thread_safe (0.3.6)
53
- tzinfo (1.2.3)
133
+ tzinfo (1.2.7)
54
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)
55
139
 
56
140
  PLATFORMS
57
141
  ruby
58
142
 
59
143
  DEPENDENCIES
60
- actionpack (>= 5)
61
144
  bundler (~> 1.12)
62
145
  byebug
63
146
  geared_pagination!
147
+ rails (>= 5.0)
64
148
  rake
149
+ sqlite3
65
150
 
66
151
  BUNDLED WITH
67
- 1.14.6
152
+ 1.17.2
data/README.md CHANGED
@@ -30,12 +30,57 @@ Showing page <%= @page.number %> of <%= @page.recordset.page_count %> (<%= @page
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 `:order_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
39
84
 
40
85
  ## Caching
41
86
 
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
data/bin/rails ADDED
@@ -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.2'
3
+ s.version = '1.0.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)
@@ -11,13 +11,13 @@ module GearedPagination
11
11
  end
12
12
 
13
13
  private
14
- def set_page_and_extract_portion_from(records, per_page: nil)
15
- @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)
16
16
  @page.records
17
17
  end
18
18
 
19
- def current_page_from(records, per_page: nil)
20
- 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)
21
21
  end
22
22
 
23
23
  def set_paginated_headers
@@ -29,7 +29,7 @@ module GearedPagination
29
29
  end
30
30
 
31
31
  def current_page_param
32
- params[:page].to_i > 0 ? params[:page].to_i : 1
32
+ params[:page]
33
33
  end
34
34
  end
35
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
@@ -1,4 +1,4 @@
1
- require 'rails/railtie'
1
+ require 'rails/engine'
2
2
  require 'geared_pagination/controller'
3
3
 
4
4
  class GearedPagination::Engine < ::Rails::Engine
@@ -7,29 +7,36 @@ module GearedPagination
7
7
  end
8
8
 
9
9
  def apply
10
- @controller.headers.update(headers) if applicable?
10
+ controller.headers.update(headers) if applicable?
11
11
  end
12
12
 
13
13
  private
14
+ attr_reader :page, :controller
15
+ delegate :request, to: :controller
16
+
14
17
  def headers
15
18
  Hash.new.tap do |h|
16
- h["X-Total-Count"] = @page.recordset.records_count.to_s
17
- h["Link"] = next_page_link_header unless @page.last?
19
+ h["X-Total-Count"] = page.recordset.records_count.to_s
20
+ h["Link"] = next_page_link_header unless page.last?
18
21
  end
19
22
  end
20
23
 
21
24
  def applicable?
22
- @controller.request.format&.json?
25
+ request.format&.json?
23
26
  end
24
27
 
25
28
  def next_page_link_header
26
- link_header(rel: :next, page_number: @page.next_number).to_s
29
+ link_header(rel: :next, page: page.next_param).to_s
30
+ end
31
+
32
+ def link_header(rel:, page:)
33
+ %{<#{uri(page: page)}>; rel="#{rel}"}
27
34
  end
28
35
 
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}"}
36
+ def uri(page:)
37
+ Addressable::URI.parse(request.url).tap do |uri|
38
+ uri.query_values = (uri.query_values || {}).merge("page" => page)
39
+ end.to_s
33
40
  end
34
41
  end
35
42
  end