api_only_pagination 0.1.4 → 0.2.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.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +5 -1
- data/CHANGELOG.md +17 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +30 -33
- data/README.md +66 -25
- data/Rakefile +3 -1
- data/lib/api_only_pagination/active_record_paginate.rb +11 -5
- data/lib/api_only_pagination/configuration.rb +3 -1
- data/lib/api_only_pagination/controller.rb +41 -0
- data/lib/api_only_pagination/paginate.rb +14 -14
- data/lib/api_only_pagination/paginated_data.rb +25 -3
- data/lib/api_only_pagination/pagination.rb +10 -1
- data/lib/api_only_pagination/version.rb +1 -1
- data/lib/api_only_pagination.rb +7 -1
- metadata +8 -21
- data/api_only_pagination.gemspec +0 -40
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 720784fef8ffa7a27fb958bcc70ae7389a967f99be74da4661e09202fec813ce
|
|
4
|
+
data.tar.gz: 1978db31fad9115663f84853648a158bac002c7f55a99fbfc21037124d026b24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a445371db801c78c7581c12fcd3078afa5ce3d8aa4794ed15584995acec291681619433adba036afa92809bc07e1217123439792a4da1af258794b40dc543825
|
|
7
|
+
data.tar.gz: b0db20fc6d8e4967e8f566e7768798fa27dbe5e9d2b815181f360cd0222d444c691086d80b6e0b180adb3c70ffa5e8e78061ebf8e720a8b0fc5f8902be232db7
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-10-30
|
|
4
|
+
|
|
5
|
+
- **Breaking:** require Ruby >= 3.2 and ActiveRecord >= 6.1 (the versions CI tests)
|
|
6
|
+
- **Breaking:** remove the `paginate` alias, which clashed with will_paginate; use `do_paginate` or `paginated_response`
|
|
7
|
+
- Add `next_page` and `previous_page` to the response
|
|
8
|
+
- Add `PaginatedData#to_h` and an explicit `#as_json`; serializer options such as `only:` apply to the records in `data`
|
|
9
|
+
- Add `render_paginated` controller helper (ActionController::Base and ::API) that reads `page`/`per_page` from params, with opt-in `X-Total-Count`/`Link` pagination headers (`pagination_headers` setting)
|
|
10
|
+
- Note: Rails 8.1 does not load on Ruby 3.3.0 because of a Ruby parser bug fixed in 3.3.1; use a later 3.3.x
|
|
11
|
+
|
|
12
|
+
## [0.1.5] - 2026-10-30
|
|
13
|
+
|
|
14
|
+
- Fix: negative `page`/`per_page` values produced invalid SQL; any value that isn't a positive integer now falls back to the configured default
|
|
15
|
+
- Fix: paginating relations with `group` or a custom `select` raised errors while counting
|
|
16
|
+
- Fix: `last_page?` returned false for empty results and for pages past the end
|
|
17
|
+
- Remove a no-op `ActiveRecord::Relation.include`; chained calls such as `User.where(...).do_paginate` are unaffected
|
|
18
|
+
- Add a real spec suite (in-memory SQLite) and CI across Ruby 3.2–3.4 and ActiveRecord 6.1–8.1
|
|
19
|
+
|
|
3
20
|
## [0.1.0] - 2023-03-23
|
|
4
21
|
|
|
5
22
|
- Initial release
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
api_only_pagination (0.
|
|
5
|
-
activerecord (>=
|
|
4
|
+
api_only_pagination (0.2.0)
|
|
5
|
+
activerecord (>= 6.1)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
@@ -32,39 +32,39 @@ GEM
|
|
|
32
32
|
tzinfo (~> 2.0)
|
|
33
33
|
zeitwerk (~> 2.3)
|
|
34
34
|
ast (2.4.2)
|
|
35
|
-
builder (3.
|
|
35
|
+
builder (3.3.0)
|
|
36
36
|
concurrent-ruby (1.2.2)
|
|
37
|
-
crass (1.0.
|
|
37
|
+
crass (1.0.8)
|
|
38
38
|
diff-lcs (1.5.0)
|
|
39
|
-
erubi (1.
|
|
39
|
+
erubi (1.13.1)
|
|
40
40
|
i18n (1.13.0)
|
|
41
41
|
concurrent-ruby (~> 1.0)
|
|
42
42
|
json (2.6.1)
|
|
43
|
-
loofah (2.
|
|
43
|
+
loofah (2.25.2)
|
|
44
44
|
crass (~> 1.0.2)
|
|
45
|
-
nokogiri (>= 1.
|
|
46
|
-
method_source (1.0.0)
|
|
45
|
+
nokogiri (>= 1.12.0)
|
|
47
46
|
minitest (5.18.0)
|
|
48
|
-
nokogiri (1.
|
|
47
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
48
|
+
racc (~> 1.4)
|
|
49
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
50
|
+
racc (~> 1.4)
|
|
51
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
49
52
|
racc (~> 1.4)
|
|
50
53
|
parallel (1.21.0)
|
|
51
|
-
parser (3.
|
|
54
|
+
parser (3.3.13.0)
|
|
52
55
|
ast (~> 2.4.1)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
rack
|
|
56
|
+
racc
|
|
57
|
+
racc (1.8.1)
|
|
58
|
+
rack (2.2.25)
|
|
59
|
+
rack-test (2.3.0)
|
|
56
60
|
rack (>= 1.3)
|
|
57
|
-
rails-dom-testing (2.0
|
|
58
|
-
activesupport (>=
|
|
61
|
+
rails-dom-testing (2.3.0)
|
|
62
|
+
activesupport (>= 5.0.0)
|
|
63
|
+
minitest
|
|
59
64
|
nokogiri (>= 1.6)
|
|
60
|
-
rails-html-sanitizer (1.
|
|
61
|
-
loofah (~> 2.
|
|
62
|
-
|
|
63
|
-
actionpack (= 6.1.7.3)
|
|
64
|
-
activesupport (= 6.1.7.3)
|
|
65
|
-
method_source
|
|
66
|
-
rake (>= 12.2)
|
|
67
|
-
thor (~> 1.0)
|
|
65
|
+
rails-html-sanitizer (1.7.1)
|
|
66
|
+
loofah (~> 2.25, >= 2.25.2)
|
|
67
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
68
68
|
rainbow (3.1.1)
|
|
69
69
|
rake (13.0.6)
|
|
70
70
|
regexp_parser (2.3.0)
|
|
@@ -81,14 +81,6 @@ GEM
|
|
|
81
81
|
rspec-mocks (3.10.2)
|
|
82
82
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
83
83
|
rspec-support (~> 3.10.0)
|
|
84
|
-
rspec-rails (4.0.2)
|
|
85
|
-
actionpack (>= 4.2)
|
|
86
|
-
activesupport (>= 4.2)
|
|
87
|
-
railties (>= 4.2)
|
|
88
|
-
rspec-core (~> 3.10)
|
|
89
|
-
rspec-expectations (~> 3.10)
|
|
90
|
-
rspec-mocks (~> 3.10)
|
|
91
|
-
rspec-support (~> 3.10)
|
|
92
84
|
rspec-support (3.10.2)
|
|
93
85
|
rubocop (1.48.1)
|
|
94
86
|
json (~> 2.3)
|
|
@@ -103,21 +95,26 @@ GEM
|
|
|
103
95
|
rubocop-ast (1.27.0)
|
|
104
96
|
parser (>= 3.2.1.0)
|
|
105
97
|
ruby-progressbar (1.11.0)
|
|
106
|
-
|
|
98
|
+
sqlite3 (1.7.3-arm64-darwin)
|
|
99
|
+
sqlite3 (1.7.3-x86_64-darwin)
|
|
100
|
+
sqlite3 (1.7.3-x86_64-linux)
|
|
107
101
|
tzinfo (2.0.6)
|
|
108
102
|
concurrent-ruby (~> 1.0)
|
|
109
103
|
unicode-display_width (2.4.2)
|
|
110
104
|
zeitwerk (2.6.8)
|
|
111
105
|
|
|
112
106
|
PLATFORMS
|
|
107
|
+
arm64-darwin-25
|
|
113
108
|
x86_64-darwin-20
|
|
109
|
+
x86_64-linux
|
|
114
110
|
|
|
115
111
|
DEPENDENCIES
|
|
112
|
+
actionpack
|
|
116
113
|
api_only_pagination!
|
|
117
114
|
rake (~> 13.0)
|
|
118
115
|
rspec (~> 3.6)
|
|
119
|
-
rspec-rails (~> 4.0)
|
|
120
116
|
rubocop (~> 1.21)
|
|
117
|
+
sqlite3 (~> 1.4)
|
|
121
118
|
|
|
122
119
|
BUNDLED WITH
|
|
123
120
|
2.4.9
|
data/README.md
CHANGED
|
@@ -9,45 +9,86 @@ or add it to your Gemfile
|
|
|
9
9
|
```
|
|
10
10
|
gem 'api_only_pagination'
|
|
11
11
|
```
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
Ruby 3.2+ and ActiveRecord 6.1+. Versions 0.1.x support older Rubies and Rails but are no longer maintained.
|
|
15
|
+
|
|
12
16
|
## Usage
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
Optionally configure defaults in `config/initializers/api_only_pagination.rb`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
ApiOnlyPagination.configure do |config|
|
|
22
|
+
config.default_page_size = 20 # per_page when none (or an invalid value) is given
|
|
23
|
+
config.max_per_page = 100 # upper limit for per_page
|
|
24
|
+
config.default_page = 1 # page when none (or an invalid value) is given
|
|
25
|
+
config.pagination_headers = false # see "In controllers"
|
|
26
|
+
end
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Call `do_paginate(page, per_page)` on any model or relation:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
result = User.where(active: true).order(:id).do_paginate(params[:page], params[:per_page])
|
|
33
|
+
render json: result
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`page` and `per_page` can come straight from request params. Strings are converted to integers, any value that isn't a positive integer falls back to the configured default, and `per_page` is capped at `max_per_page`.
|
|
37
|
+
|
|
38
|
+
`do_paginate` returns an `ApiOnlyPagination::PaginatedData` object. It renders as:
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"data": [{ "id": 3, "name": "..." }, { "id": 4, "name": "..." }],
|
|
43
|
+
"per_page": 2,
|
|
44
|
+
"current_page": 2,
|
|
45
|
+
"total_pages": 3,
|
|
46
|
+
"total_records": 5,
|
|
47
|
+
"next_page": 3,
|
|
48
|
+
"previous_page": 1
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`next_page` and `previous_page` are `null` on the last and first page. Serializer options apply to the records, so `render json: result, only: [:id, :name]` limits the fields in `data` and keeps the pagination fields. Use `result.to_h` for a symbol-keyed hash, or read the attributes directly (`result.data`, `result.total_pages`, ...).
|
|
53
|
+
|
|
54
|
+
### In controllers
|
|
55
|
+
|
|
56
|
+
`render_paginated` reads `page` and `per_page` from params, paginates, renders the JSON above and returns the result:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
class UsersController < ApplicationController
|
|
60
|
+
def index
|
|
61
|
+
render_paginated User.where(active: true).order(:id)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Other options go to `render`, so `render_paginated scope, only: [:id, :name], status: :ok` works. Pass `page:` or `per_page:` to override params.
|
|
67
|
+
|
|
68
|
+
The helper can also set pagination headers: `X-Total-Count`, `X-Total-Pages`, `X-Per-Page`, `X-Page`, and a `Link` header with `first`/`prev`/`next`/`last` URLs that keep the request's other query params. Turn them on for one call with `render_paginated scope, pagination_headers: true`, or for every call:
|
|
23
69
|
|
|
24
|
-
|
|
70
|
+
```ruby
|
|
71
|
+
ApiOnlyPagination.configure do |config|
|
|
72
|
+
config.pagination_headers = true
|
|
73
|
+
end
|
|
25
74
|
```
|
|
26
|
-
response = User.do_paginate(1,20)
|
|
27
|
-
```
|
|
28
75
|
|
|
29
|
-
|
|
76
|
+
If browser clients need to read these headers in a cross-origin setup, add them to `expose` in your CORS config.
|
|
30
77
|
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
"data" => [],
|
|
34
|
-
"per_page" => 3,
|
|
35
|
-
"current_page" => 1,
|
|
36
|
-
"total_pages" => 2,
|
|
37
|
-
"total_records" => 5
|
|
38
|
-
}
|
|
39
|
-
|
|
78
|
+
### Upgrading from 0.1.x
|
|
40
79
|
|
|
80
|
+
- The `paginate` alias was removed because it clashes with will_paginate. Use `do_paginate` (or `paginated_response`).
|
|
81
|
+
- The response has two new fields, `next_page` and `previous_page`.
|
|
41
82
|
|
|
42
83
|
## Development
|
|
43
84
|
|
|
44
|
-
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
85
|
+
After checking out the repo, run `bin/setup` to install dependencies, then `bundle exec rake` to run the specs and RuboCop. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
45
86
|
|
|
46
87
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
47
88
|
|
|
48
89
|
## Contributing
|
|
49
90
|
|
|
50
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
91
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ashok1/api_only_pagination. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/ashok1/api_only_pagination/blob/main/CODE_OF_CONDUCT.md).
|
|
51
92
|
|
|
52
93
|
## License
|
|
53
94
|
|
|
@@ -55,4 +96,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
|
55
96
|
|
|
56
97
|
## Code of Conduct
|
|
57
98
|
|
|
58
|
-
Everyone interacting in the ApiOnlyPagination project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
|
99
|
+
Everyone interacting in the ApiOnlyPagination project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ashok1/api_only_pagination/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
CHANGED
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
require "active_record"
|
|
4
4
|
|
|
5
5
|
module ApiOnlyPagination
|
|
6
|
+
# Adds do_paginate (and its aliases) to every ActiveRecord model and, through delegation, to relations
|
|
6
7
|
module ActiveRecordPaginate
|
|
7
8
|
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
# Pagination entry points available on models and relations
|
|
8
11
|
module ClassMethods
|
|
9
|
-
%i[do_paginate
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
%i[do_paginate paginated_response].each do |access_method|
|
|
13
|
+
# nil page/per_page fall back to the configured defaults in Paginate
|
|
14
|
+
define_method(access_method) do |page = nil, per_page = nil|
|
|
15
|
+
pagination = ApiOnlyPagination::Pagination.new(all, { page:, per_page: })
|
|
12
16
|
meta_data = pagination.metadata
|
|
13
17
|
|
|
14
18
|
ApiOnlyPagination::PaginatedData
|
|
@@ -16,9 +20,11 @@ module ApiOnlyPagination
|
|
|
16
20
|
per_page: meta_data.per_page,
|
|
17
21
|
current_page: meta_data.page,
|
|
18
22
|
total_pages: meta_data.total_pages,
|
|
19
|
-
total_records: meta_data.count
|
|
23
|
+
total_records: meta_data.count,
|
|
24
|
+
next_page: meta_data.next_page,
|
|
25
|
+
previous_page: meta_data.previous_page)
|
|
20
26
|
end
|
|
21
|
-
end
|
|
27
|
+
end
|
|
22
28
|
end
|
|
23
29
|
end
|
|
24
30
|
end
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ApiOnlyPagination
|
|
4
|
+
# Global settings, changed through ApiOnlyPagination.configure
|
|
4
5
|
class Configuration
|
|
5
|
-
attr_accessor :default_page_size, :max_per_page, :default_page
|
|
6
|
+
attr_accessor :default_page_size, :max_per_page, :default_page, :pagination_headers
|
|
6
7
|
|
|
7
8
|
def initialize
|
|
8
9
|
@default_page_size = 20
|
|
9
10
|
@max_per_page = 100
|
|
10
11
|
@default_page = 1
|
|
12
|
+
@pagination_headers = false
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ApiOnlyPagination
|
|
4
|
+
# Controller helper, included into ActionController::Base and ActionController::API on load
|
|
5
|
+
module Controller
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# Paginates scope with page/per_page from params, renders it as JSON and returns the PaginatedData.
|
|
9
|
+
# Remaining options go to render, e.g. render_paginated(User.all, only: [:id, :name], status: :ok)
|
|
10
|
+
def render_paginated(scope, page: params[:page], per_page: params[:per_page],
|
|
11
|
+
pagination_headers: ApiOnlyPagination.configuration.pagination_headers, **render_options)
|
|
12
|
+
result = scope.do_paginate(page, per_page)
|
|
13
|
+
add_pagination_headers(result) if pagination_headers
|
|
14
|
+
render(json: result, **render_options)
|
|
15
|
+
result
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_pagination_headers(result)
|
|
19
|
+
{
|
|
20
|
+
"X-Total-Count" => result.total_records,
|
|
21
|
+
"X-Total-Pages" => result.total_pages,
|
|
22
|
+
"X-Per-Page" => result.per_page,
|
|
23
|
+
"X-Page" => result.current_page,
|
|
24
|
+
"Link" => pagination_link_header(result).presence
|
|
25
|
+
}.compact.each { |name, value| response.headers[name] = value.to_s }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def pagination_link_header(result)
|
|
29
|
+
return "" if result.total_pages.zero?
|
|
30
|
+
|
|
31
|
+
{ first: 1, prev: result.previous_page, next: result.next_page, last: result.total_pages }
|
|
32
|
+
.filter_map { |rel, page| %(<#{pagination_url(page, result.per_page)}>; rel="#{rel}") if page }
|
|
33
|
+
.join(", ")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def pagination_url(page, per_page)
|
|
37
|
+
query = request.query_parameters.merge("page" => page, "per_page" => per_page).to_query
|
|
38
|
+
"#{request.base_url}#{request.path}?#{query}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ApiOnlyPagination
|
|
4
|
+
# Normalizes page/per_page input and computes page metadata from a record count
|
|
4
5
|
class Paginate
|
|
5
6
|
attr_reader :page, :count, :per_page
|
|
6
7
|
|
|
7
8
|
def initialize(page, per_page, count)
|
|
8
|
-
|
|
9
|
+
config = ApiOnlyPagination.configuration
|
|
10
|
+
@page = positive_integer(page) || config.default_page
|
|
9
11
|
@count = count
|
|
10
|
-
@per_page = [(per_page
|
|
11
|
-
ApiOnlyPagination.configuration.max_per_page].min
|
|
12
|
-
|
|
13
|
-
validate_params
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def validate_params
|
|
17
|
-
@page = @page.zero? ? ApiOnlyPagination.configuration.default_page : @page
|
|
18
|
-
@per_page = @per_page.zero? ? ApiOnlyPagination.configuration.default_page_size : @per_page
|
|
12
|
+
@per_page = [positive_integer(per_page) || config.default_page_size, config.max_per_page].min
|
|
19
13
|
end
|
|
20
14
|
|
|
21
15
|
def offset
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
per_page * (page.to_i - 1)
|
|
16
|
+
per_page * (page - 1)
|
|
25
17
|
end
|
|
26
18
|
|
|
27
19
|
def next_page
|
|
@@ -41,7 +33,7 @@ module ApiOnlyPagination
|
|
|
41
33
|
end
|
|
42
34
|
|
|
43
35
|
def last_page?
|
|
44
|
-
page
|
|
36
|
+
page >= total_pages
|
|
45
37
|
end
|
|
46
38
|
|
|
47
39
|
def first_page?
|
|
@@ -51,5 +43,13 @@ module ApiOnlyPagination
|
|
|
51
43
|
def total_pages
|
|
52
44
|
(count / per_page.to_f).ceil
|
|
53
45
|
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# nil, zero, negative and non-numeric values (e.g. "abc") all become nil
|
|
50
|
+
def positive_integer(value)
|
|
51
|
+
integer = value.to_i
|
|
52
|
+
integer if integer.positive?
|
|
53
|
+
end
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -2,20 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
# Main module
|
|
4
4
|
module ApiOnlyPagination
|
|
5
|
-
# Response object
|
|
5
|
+
# Response object returned by do_paginate; serializes to the documented JSON shape
|
|
6
6
|
class PaginatedData
|
|
7
7
|
attr_accessor :data,
|
|
8
8
|
:per_page,
|
|
9
9
|
:current_page,
|
|
10
10
|
:total_pages,
|
|
11
|
-
:total_records
|
|
11
|
+
:total_records,
|
|
12
|
+
:next_page,
|
|
13
|
+
:previous_page
|
|
12
14
|
|
|
13
|
-
def initialize(data:, per_page:, current_page:, total_pages:, total_records:)
|
|
15
|
+
def initialize(data:, per_page:, current_page:, total_pages:, total_records:, next_page: nil, previous_page: nil) # rubocop:disable Metrics/ParameterLists
|
|
14
16
|
@data = data
|
|
15
17
|
@per_page = per_page
|
|
16
18
|
@current_page = current_page
|
|
17
19
|
@total_pages = total_pages
|
|
18
20
|
@total_records = total_records
|
|
21
|
+
@next_page = next_page
|
|
22
|
+
@previous_page = previous_page
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_h
|
|
26
|
+
{
|
|
27
|
+
data:,
|
|
28
|
+
per_page:,
|
|
29
|
+
current_page:,
|
|
30
|
+
total_pages:,
|
|
31
|
+
total_records:,
|
|
32
|
+
next_page:,
|
|
33
|
+
previous_page:
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Serializer options (only:, except:, include:, methods:) apply to the records in data,
|
|
38
|
+
# so `render json: result, only: [:id, :name]` works as it would for the bare relation.
|
|
39
|
+
def as_json(options = nil)
|
|
40
|
+
to_h.merge(data: data.as_json(options)).as_json
|
|
19
41
|
end
|
|
20
42
|
end
|
|
21
43
|
end
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module ApiOnlyPagination
|
|
4
|
+
# Counts a relation and applies limit/offset for the requested page
|
|
4
5
|
class Pagination
|
|
5
6
|
attr_reader :collection, :params
|
|
6
7
|
|
|
7
8
|
def initialize(collection, params = {})
|
|
8
9
|
@collection = collection
|
|
9
|
-
@params = params.merge(count:
|
|
10
|
+
@params = params.merge(count: total_count)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
def metadata
|
|
@@ -18,5 +19,13 @@ module ApiOnlyPagination
|
|
|
18
19
|
.limit(metadata.per_page)
|
|
19
20
|
.offset(metadata.offset)
|
|
20
21
|
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# count(:all) ignores custom selects; grouped relations return a Hash of group => count
|
|
26
|
+
def total_count
|
|
27
|
+
count = collection.count(:all)
|
|
28
|
+
count.is_a?(Hash) ? count.size : count
|
|
29
|
+
end
|
|
21
30
|
end
|
|
22
31
|
end
|
data/lib/api_only_pagination.rb
CHANGED
|
@@ -7,7 +7,9 @@ require_relative "api_only_pagination/paginate"
|
|
|
7
7
|
require_relative "api_only_pagination/pagination"
|
|
8
8
|
require_relative "api_only_pagination/paginated_data"
|
|
9
9
|
require_relative "api_only_pagination/active_record_paginate"
|
|
10
|
+
require_relative "api_only_pagination/controller"
|
|
10
11
|
|
|
12
|
+
# Offset pagination for ActiveRecord that returns data plus page metadata for API responses
|
|
11
13
|
module ApiOnlyPagination
|
|
12
14
|
class << self
|
|
13
15
|
def configuration
|
|
@@ -26,5 +28,9 @@ end
|
|
|
26
28
|
|
|
27
29
|
ActiveSupport.on_load(:active_record) do
|
|
28
30
|
include ApiOnlyPagination::ActiveRecordPaginate
|
|
29
|
-
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Fires for both ActionController::Base and ActionController::API; does nothing in apps without ActionPack
|
|
34
|
+
ActiveSupport.on_load(:action_controller) do
|
|
35
|
+
include ApiOnlyPagination::Controller
|
|
30
36
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: api_only_pagination
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ashok
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-10-30 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '6.1'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '6.1'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: rspec
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,20 +38,6 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '3.6'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: rspec-rails
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: '4.0'
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '4.0'
|
|
55
41
|
description: pagination with api response only
|
|
56
42
|
email:
|
|
57
43
|
- kingkoirala1@gmail.com
|
|
@@ -59,6 +45,7 @@ executables: []
|
|
|
59
45
|
extensions: []
|
|
60
46
|
extra_rdoc_files: []
|
|
61
47
|
files:
|
|
48
|
+
- ".rspec"
|
|
62
49
|
- ".rubocop.yml"
|
|
63
50
|
- CHANGELOG.md
|
|
64
51
|
- CODE_OF_CONDUCT.md
|
|
@@ -67,10 +54,10 @@ files:
|
|
|
67
54
|
- LICENSE.txt
|
|
68
55
|
- README.md
|
|
69
56
|
- Rakefile
|
|
70
|
-
- api_only_pagination.gemspec
|
|
71
57
|
- lib/api_only_pagination.rb
|
|
72
58
|
- lib/api_only_pagination/active_record_paginate.rb
|
|
73
59
|
- lib/api_only_pagination/configuration.rb
|
|
60
|
+
- lib/api_only_pagination/controller.rb
|
|
74
61
|
- lib/api_only_pagination/paginate.rb
|
|
75
62
|
- lib/api_only_pagination/paginated_data.rb
|
|
76
63
|
- lib/api_only_pagination/pagination.rb
|
|
@@ -90,14 +77,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
90
77
|
requirements:
|
|
91
78
|
- - ">="
|
|
92
79
|
- !ruby/object:Gem::Version
|
|
93
|
-
version: 2.
|
|
80
|
+
version: 3.2.0
|
|
94
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
82
|
requirements:
|
|
96
83
|
- - ">="
|
|
97
84
|
- !ruby/object:Gem::Version
|
|
98
85
|
version: '0'
|
|
99
86
|
requirements: []
|
|
100
|
-
rubygems_version: 3.
|
|
87
|
+
rubygems_version: 3.5.3
|
|
101
88
|
signing_key:
|
|
102
89
|
specification_version: 4
|
|
103
90
|
summary: pagination with api response only
|
data/api_only_pagination.gemspec
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "lib/api_only_pagination/version"
|
|
4
|
-
|
|
5
|
-
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name = "api_only_pagination"
|
|
7
|
-
spec.version = ApiOnlyPagination::VERSION
|
|
8
|
-
spec.authors = ["Ashok"]
|
|
9
|
-
spec.email = ["kingkoirala1@gmail.com"]
|
|
10
|
-
|
|
11
|
-
spec.summary = "pagination with api response only"
|
|
12
|
-
spec.description = "pagination with api response only"
|
|
13
|
-
spec.homepage = "https://github.com/ashok1/api_only_pagination"
|
|
14
|
-
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = ">= 2.6.0"
|
|
16
|
-
|
|
17
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
|
18
|
-
|
|
19
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
|
20
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
|
21
|
-
|
|
22
|
-
# Specify which files should be added to the gem when it is released.
|
|
23
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
24
|
-
spec.files = Dir.chdir(__dir__) do
|
|
25
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
|
26
|
-
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
spec.bindir = "exe"
|
|
30
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
31
|
-
spec.require_paths = ["lib"]
|
|
32
|
-
spec.add_dependency "activerecord", ">= 5.0"
|
|
33
|
-
spec.add_development_dependency "rspec", "~> 3.6"
|
|
34
|
-
spec.add_development_dependency "rspec-rails", "~> 4.0"
|
|
35
|
-
# Uncomment to register a new dependency of your gem
|
|
36
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
|
37
|
-
|
|
38
|
-
# For more information and examples about making a new gem, check out our
|
|
39
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
|
40
|
-
end
|