next_page 0.2.0 → 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.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +97 -84
- data/lib/next_page/configuration.rb +5 -28
- data/lib/next_page/pagination.rb +8 -56
- data/lib/next_page/pagination_attributes.rb +14 -4
- data/lib/next_page/paginator.rb +11 -76
- data/lib/next_page/version.rb +3 -1
- data/lib/next_page.rb +0 -6
- metadata +25 -64
- data/MIT-LICENSE +0 -20
- data/lib/next_page/exceptions/invalid_nested_sort.rb +0 -14
- data/lib/next_page/exceptions/invalid_sort_parameter.rb +0 -13
- data/lib/next_page/exceptions.rb +0 -11
- data/lib/next_page/sort/name_evaluator.rb +0 -60
- data/lib/next_page/sort/segment_parser.rb +0 -33
- data/lib/next_page/sort/sort_builder.rb +0 -118
- data/lib/next_page/sorter.rb +0 -49
- data/lib/tasks/next_page_tasks.rake +0 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1cc73f97e32970013870bc90c44060f70a64bdd296a7a62591b0f5a1eba363ba
|
|
4
|
+
data.tar.gz: 628eee3dedc5b340a20aebee307046118b2c798a0c523ec32d4abf80ac552223
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d919ec3c531bd509a49bb250ee5bda3062c377cdcdda9ee9de25c3e8db3ae4e2405d65227d76b254f9978cf62e8b7741a7a824d53defe75c5c98b62e6d784d5a
|
|
7
|
+
data.tar.gz: 30dec7d71f321f5834c6b91450dacfe7397a2597dbfe19b13f0ed9e73085b0c5791fc3359625de09a5815656403772f05030648fb502305556ac3a5300de2af0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Rockridge Solutions
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -4,43 +4,72 @@
|
|
|
4
4
|
[](https://codeclimate.com/github/RockSolt/next_page/maintainability)
|
|
5
5
|
|
|
6
6
|
# NextPage
|
|
7
|
-
Basic pagination for Rails controllers.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
NextPage provides simple pagination with no frills in less than 100 lines of code. It reads request
|
|
9
|
+
parameters, provides an offset and limit to the query, and decorates the ActiveRecord relation
|
|
10
|
+
with pagination attributes.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
Add an include statement for the module into any controller that needs pagination:
|
|
12
|
+
No more, no less.
|
|
14
13
|
|
|
15
|
-
```ruby
|
|
16
|
-
include NextPage::Pagination
|
|
17
14
|
```
|
|
15
|
+
def index
|
|
16
|
+
@widgets = paginate_resource(Widget.all)
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Table of Contents
|
|
21
|
+
|
|
22
|
+
- [Getting Started](#getting-started)
|
|
23
|
+
- [Usage](#usage)
|
|
24
|
+
- [Include the Module](#include-the-module)
|
|
25
|
+
- [Invoking Pagination](#invoking-pagination)
|
|
26
|
+
- [Link Helpers](#link-helpers)
|
|
27
|
+
- [Count Query](#count-query)
|
|
28
|
+
- [Request Parameters](#request-parameters)
|
|
29
|
+
- [Default Results per Action](#default-results-per-action)
|
|
30
|
+
- [Configuration](#configuration)
|
|
31
|
+
- [Contribute](#contribute)
|
|
32
|
+
- [Running Tests](#running-tests)
|
|
33
|
+
- [License](#license)
|
|
18
34
|
|
|
19
|
-
|
|
35
|
+
## Getting Started
|
|
20
36
|
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
This gem requires Rails 7.2+ and works with ActiveRecord.
|
|
38
|
+
|
|
39
|
+
### Installation
|
|
40
|
+
|
|
41
|
+
Add this line to your application's Gemfile:
|
|
23
42
|
|
|
24
43
|
```ruby
|
|
25
|
-
|
|
44
|
+
gem 'next_page'
|
|
26
45
|
```
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
And then execute:
|
|
48
|
+
```bash
|
|
49
|
+
$ bundle
|
|
50
|
+
```
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
Or install it yourself as:
|
|
53
|
+
```bash
|
|
54
|
+
$ gem install next_page
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
Module NextPage::Pagination provides pagination controllers. It assigns a limit and offset to the
|
|
60
|
+
resource query and extends the relation with mixin NextPage::PaginationAttributes to provide helper
|
|
61
|
+
methods for generating links.
|
|
62
|
+
|
|
63
|
+
### Include the Module
|
|
64
|
+
|
|
65
|
+
Add an include statement for the module into any controller that needs pagination (or the ApplicationController):
|
|
35
66
|
|
|
36
67
|
```ruby
|
|
37
|
-
|
|
68
|
+
include NextPage::Pagination
|
|
38
69
|
```
|
|
39
70
|
|
|
40
|
-
|
|
41
|
-
that removes pagination.
|
|
71
|
+
### Invoking Pagination
|
|
42
72
|
|
|
43
|
-
### Invoking Pagination Directly
|
|
44
73
|
To paginate a resource pass the resource into method `paginate_resource` then store the return value back in the
|
|
45
74
|
resource:
|
|
46
75
|
|
|
@@ -48,98 +77,82 @@ resource:
|
|
|
48
77
|
@photos = paginate_resource(@photos)
|
|
49
78
|
```
|
|
50
79
|
|
|
51
|
-
|
|
52
|
-
Requests can specify sort order using the parameter `sort` with an attribute name or scope. Sorts can be prefixed with `+` or `-` to indicate ascending or descending. Multiple sorts can be specified either as a comma separated list or via bracket notation.
|
|
53
|
-
|
|
54
|
-
/photos?sort=-created_at
|
|
55
|
-
/photos?sort=location,-created_by
|
|
56
|
-
/photos?sort[]=location&photos[]=-created_by
|
|
80
|
+
The resource is decorated, so this needs to be the last step before rendering.
|
|
57
81
|
|
|
58
|
-
|
|
82
|
+
### Link Helpers
|
|
59
83
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
This gem does not do any rendering. It does provide helper methods for generating links. The resource will include the following additional methods:
|
|
85
|
+
- previous_page
|
|
86
|
+
- current_page
|
|
87
|
+
- next_page
|
|
88
|
+
- total_pages
|
|
89
|
+
- total_count
|
|
90
|
+
- per_page
|
|
63
91
|
|
|
64
|
-
|
|
92
|
+
The `previous_page` and `last_page` readers will return `nil` on the first and last page, respectively.
|
|
65
93
|
|
|
66
|
-
|
|
94
|
+
#### Count Query
|
|
67
95
|
|
|
68
|
-
|
|
69
|
-
|
|
96
|
+
In some cases (such as grouping), calling count on the query does not provide an accurate representation. If that is the case, then there are two ways to override the default behavior:
|
|
97
|
+
- provide a count_query that can resolve the attributes
|
|
98
|
+
- specify the following attributes manually: current_page, total_count, and per_page
|
|
70
99
|
|
|
71
|
-
|
|
100
|
+
### Request Parameters
|
|
72
101
|
|
|
73
|
-
In order to
|
|
102
|
+
In order to control pagingation, the request should pass the `size` and `number` parameters under the
|
|
103
|
+
`page` key:
|
|
74
104
|
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
order("CASE status WHEN 'new' THEN 1 WHEN 'in progress' THEN 2 ELSE 3 END #{direction}")
|
|
78
|
-
end
|
|
105
|
+
```
|
|
106
|
+
?page[size]=10&page[number]=2
|
|
79
107
|
```
|
|
80
108
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
In order to keep the peace between frontend and backend developers, scope names can include a prefix or suffix that the front end can ignore. For example, given a scope that sorts on a derived attribute (such as status in the _Direction Scope Sorts_ example), the backend developer might prefer to name the scope status_sort or sort_by_status, as a class method that shares the same name as an attribute might be unclear. However, the frontend developer does not want a query parameter that says <tt>sort=sort_by_status</tt>; it is an exception because it doesn't match the name of the attribute (and it's not pretty).
|
|
84
|
-
|
|
85
|
-
The configuration allows a prefix and or suffix to be specified. If either is specified, then in addition to looking for a scope that matches the parameter name, it will also look for a scope that matches the prefixed and/or suffixed name. Prefixes are defined by configuration option <tt>sort_scope_prefix</tt> and suffixes are defined by <tt>sort_scope_suffix</tt>.
|
|
109
|
+
### Default Results per Action
|
|
86
110
|
|
|
87
|
-
|
|
111
|
+
The default number of results per page can be overridden by specifying a new default with the call:
|
|
88
112
|
|
|
89
113
|
```ruby
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end
|
|
93
|
-
```
|
|
94
|
-
This allows the query parameter to be the following:
|
|
114
|
+
@photos = paginate_resource(@photos, default_limit: 25)
|
|
115
|
+
```
|
|
95
116
|
|
|
96
|
-
|
|
117
|
+
## Configuration
|
|
97
118
|
|
|
119
|
+
There is one configuration option: `default_per_page`.
|
|
98
120
|
|
|
99
|
-
|
|
100
|
-
The default size limit can be overridden with the `paginate_with` method for either type of paginagion. Pass option
|
|
101
|
-
`default_limit` to specify an override:
|
|
121
|
+
The option can be set directly...
|
|
102
122
|
|
|
103
|
-
|
|
104
|
-
paginate_with default_limit: 25
|
|
105
|
-
```
|
|
123
|
+
`NextPage.configuration.default_per_page = 25`
|
|
106
124
|
|
|
107
|
-
|
|
125
|
+
...or the configuration can be yielded:
|
|
108
126
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
127
|
+
```
|
|
128
|
+
NextPage.configure do |config|
|
|
129
|
+
config.default_per_page = 25
|
|
130
|
+
end
|
|
112
131
|
```
|
|
113
132
|
|
|
114
|
-
|
|
115
|
-
This gem does not do any rendering. It does provide helper methods for generating links. The resource will include the following additional methods (when the request header Accept is `'application/vnd.api+json'`):
|
|
116
|
-
- current_page
|
|
117
|
-
- next_page
|
|
118
|
-
- total_pages
|
|
119
|
-
- per_page
|
|
133
|
+
**Results per Page**
|
|
120
134
|
|
|
121
|
-
|
|
122
|
-
In some cases (such as grouping), calling count on the query does not provide an accurate representation. If that is the case, then there are two ways to override the default behavior:
|
|
123
|
-
- provide a count_query that can resolve the attributes
|
|
124
|
-
- specify the following attributes manually: current_page, total_count, and per_page
|
|
135
|
+
If not specified in the configuration, the default value for results per page is 12.
|
|
125
136
|
|
|
137
|
+
## Contribute
|
|
126
138
|
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
Feedback, feature requests, proposed changes, and bug reports are welcomed. Please use the
|
|
140
|
+
[issue tracker](https://github.com/RockSolt/next_page/issues) for feedback and feature requests. To
|
|
141
|
+
propose a change directly, please fork the repo and open a pull request. Keep an eye on the actions
|
|
142
|
+
to make sure the tests and Rubocop are passing. [Code Climate](https://codeclimate.com/github/RockSolt/next_page)
|
|
143
|
+
is also used manually to assess the codeline.
|
|
129
144
|
|
|
130
|
-
|
|
131
|
-
gem 'next_page'
|
|
132
|
-
```
|
|
145
|
+
### Running Tests
|
|
133
146
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
```
|
|
147
|
+
Tests are written in RSpec and the dummy app uses a sqlite database.
|
|
148
|
+
|
|
149
|
+
The tests can also be run across all the ruby and Rails combinations using appraisal. The install is a one-time step.
|
|
138
150
|
|
|
139
|
-
Or install it yourself as:
|
|
140
151
|
```bash
|
|
141
|
-
|
|
152
|
+
bundle exec appraisal install
|
|
153
|
+
bundle exec appraisal rspec
|
|
142
154
|
```
|
|
143
155
|
|
|
144
156
|
## License
|
|
157
|
+
|
|
145
158
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NextPage
|
|
4
|
-
#
|
|
4
|
+
# # Configuration
|
|
5
5
|
#
|
|
6
6
|
# Class Configuration stores the following settings:
|
|
7
|
-
# -
|
|
8
|
-
# - sort_scope_suffix
|
|
9
|
-
#
|
|
10
|
-
# == Sort Scope Prefix
|
|
11
|
-
# Enables client to sort request to be mapped to a scope with a more specific name. For example, given a derived
|
|
12
|
-
# attribute named <tt>status</tt>, the query parameter can be <tt>sort=status</tt> but would map to a more explicitly
|
|
13
|
-
# named scope, such as <tt>sort_by_status</tt> (assuming the <tt>sort_scope_prefix</tt> value is 'sort_by_').
|
|
14
|
-
#
|
|
15
|
-
# == Sort Scope Suffix
|
|
16
|
-
# Enables client to sort request to be mapped to a scope with a more specific name. For example, given a derived
|
|
17
|
-
# attribute named <tt>status</tt>, the query parameter can be <tt>sort=status</tt> but would map to a more explicitly
|
|
18
|
-
# named scope, such as <tt>status_sort</tt> (assuming the <tt>sort_scope_suffix</tt> value is '_sort').
|
|
7
|
+
# - default_per_page
|
|
19
8
|
class Configuration
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def sort_scope_prefix=(value)
|
|
23
|
-
@sort_scope_prefix = value.to_s
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def sort_scope_prefix?
|
|
27
|
-
@sort_scope_prefix.present?
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def sort_scope_suffix=(value)
|
|
31
|
-
@sort_scope_suffix = value.to_s
|
|
32
|
-
end
|
|
9
|
+
attr_accessor :default_per_page
|
|
33
10
|
|
|
34
|
-
def
|
|
35
|
-
@
|
|
11
|
+
def initialize
|
|
12
|
+
@default_per_page = 12
|
|
36
13
|
end
|
|
37
14
|
end
|
|
38
15
|
end
|
data/lib/next_page/pagination.rb
CHANGED
|
@@ -1,80 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'active_support/concern'
|
|
4
|
+
|
|
3
5
|
module NextPage
|
|
4
|
-
#
|
|
6
|
+
# # Pagination
|
|
5
7
|
#
|
|
6
|
-
# Module Pagination provides pagination for
|
|
8
|
+
# Module Pagination provides pagination for ActiveRecord queries. It assigns a limit and offset
|
|
7
9
|
# to the resource query and extends the relation with mixin NextPage::PaginationAttributes.
|
|
8
10
|
#
|
|
9
|
-
# There are two ways to paginate: using a before filter or by calling `paginate_resource` explicitly.
|
|
10
|
-
#
|
|
11
|
-
# == Before Filter
|
|
12
|
-
# Here's an example of using the before filter in a controller:
|
|
13
|
-
# before_action :apply_next_page_pagination, only: :index
|
|
14
|
-
#
|
|
15
|
-
# This entry point uses the following conventions to apply pagination:
|
|
16
|
-
# - the name of the instance variable is the same as the component (for example PhotosController -> @photos)
|
|
17
|
-
# - the name of the model is the controller name singularized (for example PhotosController -> Photo)
|
|
18
|
-
#
|
|
19
|
-
# Either can be overridden by calling method `paginate_with` in the controller. The two override options are
|
|
20
|
-
# `instance_variable_name` and `model_class`. For example, if the PhotosController used the model Picture and the
|
|
21
|
-
# instance variable name @photographs, the controller declares it as follows:
|
|
22
|
-
# paginate_with instance_variable_name: :photographs, model_class: 'Picture'
|
|
23
11
|
#
|
|
24
|
-
#
|
|
25
|
-
# that removes pagination.
|
|
12
|
+
# ## Invoking Pagination
|
|
26
13
|
#
|
|
27
|
-
# == Invoking Pagination Directly
|
|
28
14
|
# To paginate a resource pass the resource into method `paginate_resource` then store the return value back in the
|
|
29
15
|
# resource:
|
|
30
16
|
#
|
|
31
17
|
# @photos = paginate_resource(@photos)
|
|
32
18
|
#
|
|
33
|
-
# == Default Limit
|
|
34
|
-
# The default size limit can be overridden with the `paginate_with` method for either type of pagination. Pass option
|
|
35
|
-
# `default_limit` to specify an override:
|
|
36
|
-
#
|
|
37
|
-
# paginate_with default_limit: 25
|
|
38
|
-
#
|
|
39
|
-
# All the options can be mixed and matches when calling `paginate_with`:
|
|
40
|
-
#
|
|
41
|
-
# paginate_with model_class: 'Photo', default_limit: 12
|
|
42
|
-
# paginate_with default_limit: 12, instance_variable_name: 'data'
|
|
43
19
|
module Pagination
|
|
44
20
|
extend ActiveSupport::Concern
|
|
45
21
|
|
|
46
22
|
class_methods do
|
|
47
23
|
def next_page_paginator # :nodoc:
|
|
48
|
-
@next_page_paginator ||= NextPage::Paginator.new
|
|
24
|
+
@next_page_paginator ||= NextPage::Paginator.new
|
|
49
25
|
end
|
|
50
|
-
|
|
51
|
-
# Configure pagination with any of the following options:
|
|
52
|
-
# - instance_variable_name: explicitly name the variable if it does not follow the convention
|
|
53
|
-
# - model_class: explicitly specify the model name if it does not follow the convention
|
|
54
|
-
# - default_limit: specify an alternate default
|
|
55
|
-
# - default_sort: sort parameter if none provided, use same format as url: created_at OR -updated_at
|
|
56
|
-
def paginate_with(instance_variable_name: nil, model_class: nil, default_limit: nil, default_sort: nil)
|
|
57
|
-
next_page_paginator.paginate_with(instance_variable_name, model_class, default_limit, default_sort)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Called with before_action in order to automatically paginate the resource.
|
|
62
|
-
def apply_next_page_pagination
|
|
63
|
-
self.class.next_page_paginator.paginate(self, params.slice(:page, :sort))
|
|
64
26
|
end
|
|
65
27
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
self.class.next_page_paginator.paginate_resource(resource, params.slice(:page, :sort))
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def render(*args) # :nodoc:
|
|
72
|
-
return super unless action_name == 'index' && request.headers[:Accept] == 'application/vnd.api+json'
|
|
73
|
-
|
|
74
|
-
self.class.next_page_paginator.decorate_meta!(args.first)
|
|
75
|
-
super
|
|
76
|
-
rescue StandardError
|
|
77
|
-
super
|
|
28
|
+
def paginate_resource(resource, default_limit: nil)
|
|
29
|
+
self.class.next_page_paginator.paginate_resource(resource, params.fetch(:page, {}), default_limit)
|
|
78
30
|
end
|
|
79
31
|
end
|
|
80
32
|
end
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NextPage
|
|
4
|
-
#
|
|
4
|
+
# # Pagination Attributes
|
|
5
5
|
#
|
|
6
|
-
# Module PaginationAttributes adds in methods
|
|
6
|
+
# Module PaginationAttributes adds in methods to help with pagination links: previous_page, current_page, next_page,
|
|
7
7
|
# and total_pages. It reads the offset and limit on the query to determine the values.
|
|
8
8
|
#
|
|
9
9
|
# In some cases the query will not support count. In that case, there are two ways to override the default behavior:
|
|
10
10
|
# - provide a count_query that can resolve the attributes
|
|
11
11
|
# - specify the following attributes manually: current_page, total_count, and per_page
|
|
12
|
+
#
|
|
13
|
+
# These can be completed after the call to `paginate_resource`.
|
|
12
14
|
module PaginationAttributes
|
|
13
|
-
attr_writer :count_query, :
|
|
15
|
+
attr_writer :count_query, :total_count
|
|
16
|
+
|
|
17
|
+
def current_page=(value)
|
|
18
|
+
@current_page = [value.to_i, 1].max
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def per_page=(value)
|
|
22
|
+
@per_page = [value.to_i, 1].max
|
|
23
|
+
end
|
|
14
24
|
|
|
15
25
|
def previous_page
|
|
16
26
|
current_page > 1 ? current_page - 1 : nil
|
|
17
27
|
end
|
|
18
28
|
|
|
19
29
|
def current_page
|
|
20
|
-
@current_page ||=
|
|
30
|
+
@current_page ||= 1
|
|
21
31
|
end
|
|
22
32
|
|
|
23
33
|
def next_page
|
data/lib/next_page/paginator.rb
CHANGED
|
@@ -1,96 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module NextPage
|
|
4
|
-
#
|
|
4
|
+
# # Paginator
|
|
5
5
|
#
|
|
6
6
|
# Class Paginator uses the controller information to determine the model and variable name for the
|
|
7
7
|
# request, then applies a limit and offset to the query based upon the parameters or the defaults. It also extends
|
|
8
8
|
# the resource with the NextPage::PaginationAttributes mixin.
|
|
9
|
-
#
|
|
10
|
-
# Configuration can be specified in the controller by calling `paginate_with`. The following overrides can be
|
|
11
|
-
# specified if necessary:
|
|
12
|
-
# - default_limit: limit to use if request does not specify (default value is 10)
|
|
13
|
-
# - instance_variable_name: default value is the controller name; for example, @photos in PhotosController
|
|
14
|
-
# - model_class: default derived from controller name (or path if nested); for example, Photo for PhotosController
|
|
15
9
|
class Paginator
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def initialize(controller_name, controller_path)
|
|
19
|
-
@controller_name = controller_name
|
|
20
|
-
@controller_path = controller_path
|
|
21
|
-
|
|
22
|
-
@default_limit = DEFAULT_LIMIT
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def paginate_with(instance_variable_name, model_class, default_limit, default_sort)
|
|
26
|
-
@default_limit = default_limit if default_limit.present?
|
|
27
|
-
@instance_variable_name = instance_variable_name
|
|
28
|
-
@model_class = model_class.is_a?(String) ? model_class.constantize : model_class
|
|
29
|
-
@default_sort = default_sort
|
|
30
|
-
end
|
|
10
|
+
def paginate_resource(data, params, default_limit)
|
|
11
|
+
default_limit ||= NextPage.configuration.default_per_page
|
|
31
12
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
13
|
+
assign_pagination_attributes(
|
|
14
|
+
data,
|
|
15
|
+
per_page: params[:size]&.to_i || default_limit,
|
|
16
|
+
current_page: params[:number]&.to_i || 1
|
|
17
|
+
)
|
|
35
18
|
|
|
36
|
-
controller.instance_variable_set(name, paginate_resource(data, page_params))
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def paginate_resource(data, params)
|
|
40
|
-
assign_pagination_attributes(data, params)
|
|
41
|
-
|
|
42
|
-
data = sorter.sort(data, params.fetch(:sort, default_sort))
|
|
43
19
|
data.limit(data.per_page).offset((data.current_page - 1) * data.per_page)
|
|
44
20
|
end
|
|
45
21
|
|
|
46
|
-
def decorate_meta!(options)
|
|
47
|
-
return unless options.is_a?(Hash) && options.key?(:json) && !options[:json].is_a?(Hash)
|
|
48
|
-
|
|
49
|
-
resource = options[:json]
|
|
50
|
-
options[:meta] = options.fetch(:meta, {}).merge!(total_pages: resource.total_pages,
|
|
51
|
-
total_count: resource.total_count)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
22
|
private
|
|
55
23
|
|
|
56
|
-
def
|
|
57
|
-
@model_class ||= @controller_name.classify.safe_constantize ||
|
|
58
|
-
@controller_path.classify.safe_constantize ||
|
|
59
|
-
raise('Could not determine model for pagination; please specify using `paginate_with` options.')
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def instance_variable_name
|
|
63
|
-
@instance_variable_name ||= @controller_name
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def default_sort
|
|
67
|
-
@default_sort ||= "-#{@model_class.primary_key}"
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def assign_pagination_attributes(data, params)
|
|
24
|
+
def assign_pagination_attributes(data, per_page:, current_page:)
|
|
71
25
|
data.extend(NextPage::PaginationAttributes)
|
|
72
|
-
data.per_page = page_size(params[:page])
|
|
73
|
-
data.current_page = page_number(params[:page])
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def page_size(page)
|
|
77
|
-
if page.present? && page[:size].present?
|
|
78
|
-
page[:size]&.to_i
|
|
79
|
-
else
|
|
80
|
-
@default_limit
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def page_number(page)
|
|
85
|
-
if page.present? && page[:number].present?
|
|
86
|
-
page[:number]&.to_i
|
|
87
|
-
else
|
|
88
|
-
1
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
26
|
|
|
92
|
-
|
|
93
|
-
|
|
27
|
+
data.per_page = per_page
|
|
28
|
+
data.current_page = current_page
|
|
94
29
|
end
|
|
95
30
|
end
|
|
96
31
|
end
|
data/lib/next_page/version.rb
CHANGED
data/lib/next_page.rb
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'next_page/configuration'
|
|
4
|
-
require 'next_page/exceptions'
|
|
5
4
|
require 'next_page/pagination'
|
|
6
5
|
require 'next_page/pagination_attributes'
|
|
7
|
-
require 'next_page/sorter'
|
|
8
6
|
require 'next_page/paginator'
|
|
9
7
|
|
|
10
8
|
# = Next Page
|
|
11
9
|
module NextPage
|
|
12
|
-
class << self
|
|
13
|
-
attr_writer :configuration
|
|
14
|
-
end
|
|
15
|
-
|
|
16
10
|
def self.configuration
|
|
17
11
|
@configuration ||= Configuration.new
|
|
18
12
|
end
|
metadata
CHANGED
|
@@ -1,141 +1,112 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: next_page
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Todd Kummer
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: activerecord
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - ">="
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: '7.2'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: appraisal
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: 2.5.0
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: 2.5.0
|
|
25
|
+
version: '7.2'
|
|
41
26
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: 2.18.0
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: 2.18.0
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: guard-rspec
|
|
27
|
+
name: rails
|
|
57
28
|
requirement: !ruby/object:Gem::Requirement
|
|
58
29
|
requirements:
|
|
59
|
-
- - "
|
|
30
|
+
- - ">="
|
|
60
31
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
32
|
+
version: '7.2'
|
|
62
33
|
type: :development
|
|
63
34
|
prerelease: false
|
|
64
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
36
|
requirements:
|
|
66
|
-
- - "
|
|
37
|
+
- - ">="
|
|
67
38
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
39
|
+
version: '7.2'
|
|
69
40
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
41
|
+
name: appraisal
|
|
71
42
|
requirement: !ruby/object:Gem::Requirement
|
|
72
43
|
requirements:
|
|
73
44
|
- - "~>"
|
|
74
45
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
46
|
+
version: 2.5.0
|
|
76
47
|
type: :development
|
|
77
48
|
prerelease: false
|
|
78
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
50
|
requirements:
|
|
80
51
|
- - "~>"
|
|
81
52
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
53
|
+
version: 2.5.0
|
|
83
54
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
55
|
+
name: sqlite3
|
|
85
56
|
requirement: !ruby/object:Gem::Requirement
|
|
86
57
|
requirements:
|
|
87
58
|
- - "~>"
|
|
88
59
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 1
|
|
60
|
+
version: '2.1'
|
|
90
61
|
type: :development
|
|
91
62
|
prerelease: false
|
|
92
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
64
|
requirements:
|
|
94
65
|
- - "~>"
|
|
95
66
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 1
|
|
67
|
+
version: '2.1'
|
|
97
68
|
- !ruby/object:Gem::Dependency
|
|
98
69
|
name: rspec-rails
|
|
99
70
|
requirement: !ruby/object:Gem::Requirement
|
|
100
71
|
requirements:
|
|
101
72
|
- - "~>"
|
|
102
73
|
- !ruby/object:Gem::Version
|
|
103
|
-
version:
|
|
74
|
+
version: '8.0'
|
|
104
75
|
type: :development
|
|
105
76
|
prerelease: false
|
|
106
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
78
|
requirements:
|
|
108
79
|
- - "~>"
|
|
109
80
|
- !ruby/object:Gem::Version
|
|
110
|
-
version:
|
|
81
|
+
version: '8.0'
|
|
111
82
|
- !ruby/object:Gem::Dependency
|
|
112
83
|
name: rubocop
|
|
113
84
|
requirement: !ruby/object:Gem::Requirement
|
|
114
85
|
requirements:
|
|
115
86
|
- - "~>"
|
|
116
87
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 1.
|
|
88
|
+
version: '1.75'
|
|
118
89
|
type: :development
|
|
119
90
|
prerelease: false
|
|
120
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
92
|
requirements:
|
|
122
93
|
- - "~>"
|
|
123
94
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: 1.
|
|
95
|
+
version: '1.75'
|
|
125
96
|
- !ruby/object:Gem::Dependency
|
|
126
97
|
name: simplecov
|
|
127
98
|
requirement: !ruby/object:Gem::Requirement
|
|
128
99
|
requirements:
|
|
129
100
|
- - "~>"
|
|
130
101
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0.
|
|
102
|
+
version: '0.19'
|
|
132
103
|
type: :development
|
|
133
104
|
prerelease: false
|
|
134
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
106
|
requirements:
|
|
136
107
|
- - "~>"
|
|
137
108
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0.
|
|
109
|
+
version: '0.19'
|
|
139
110
|
description: Provide basic pagination, including page size and number as well as helpers
|
|
140
111
|
for generating links.
|
|
141
112
|
email:
|
|
@@ -144,28 +115,19 @@ executables: []
|
|
|
144
115
|
extensions: []
|
|
145
116
|
extra_rdoc_files: []
|
|
146
117
|
files:
|
|
147
|
-
-
|
|
118
|
+
- LICENSE
|
|
148
119
|
- README.md
|
|
149
120
|
- Rakefile
|
|
150
121
|
- lib/next_page.rb
|
|
151
122
|
- lib/next_page/configuration.rb
|
|
152
|
-
- lib/next_page/exceptions.rb
|
|
153
|
-
- lib/next_page/exceptions/invalid_nested_sort.rb
|
|
154
|
-
- lib/next_page/exceptions/invalid_sort_parameter.rb
|
|
155
123
|
- lib/next_page/pagination.rb
|
|
156
124
|
- lib/next_page/pagination_attributes.rb
|
|
157
125
|
- lib/next_page/paginator.rb
|
|
158
|
-
- lib/next_page/sort/name_evaluator.rb
|
|
159
|
-
- lib/next_page/sort/segment_parser.rb
|
|
160
|
-
- lib/next_page/sort/sort_builder.rb
|
|
161
|
-
- lib/next_page/sorter.rb
|
|
162
126
|
- lib/next_page/version.rb
|
|
163
|
-
- lib/tasks/next_page_tasks.rake
|
|
164
127
|
homepage: https://github.com/RockSolt/next_page
|
|
165
128
|
licenses:
|
|
166
129
|
- MIT
|
|
167
130
|
metadata: {}
|
|
168
|
-
post_install_message:
|
|
169
131
|
rdoc_options: []
|
|
170
132
|
require_paths:
|
|
171
133
|
- lib
|
|
@@ -173,15 +135,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
173
135
|
requirements:
|
|
174
136
|
- - ">="
|
|
175
137
|
- !ruby/object:Gem::Version
|
|
176
|
-
version: '
|
|
138
|
+
version: '3.2'
|
|
177
139
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
140
|
requirements:
|
|
179
141
|
- - ">="
|
|
180
142
|
- !ruby/object:Gem::Version
|
|
181
143
|
version: '0'
|
|
182
144
|
requirements: []
|
|
183
|
-
rubygems_version:
|
|
184
|
-
signing_key:
|
|
145
|
+
rubygems_version: 4.0.3
|
|
185
146
|
specification_version: 4
|
|
186
147
|
summary: Pagination for Rails Controllers
|
|
187
148
|
test_files: []
|
data/MIT-LICENSE
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
Copyright 2020 Todd Kummer
|
|
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.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module NextPage
|
|
4
|
-
module Exceptions
|
|
5
|
-
# = Invalid Nested Sort
|
|
6
|
-
class InvalidNestedSort < NextPage::Exceptions::NextPageError
|
|
7
|
-
def initialize(model, association)
|
|
8
|
-
@model = model
|
|
9
|
-
@association = association
|
|
10
|
-
super("Invalid nested sort: Unable to find association #{@association} on model #{@model}")
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module NextPage
|
|
4
|
-
module Exceptions
|
|
5
|
-
# = Invalid Sort Parameter
|
|
6
|
-
class InvalidSortParameter < NextPage::Exceptions::NextPageError
|
|
7
|
-
def initialize(segment)
|
|
8
|
-
@segment = segment
|
|
9
|
-
super("Invalid sort parameter (#{@segment}). Must be an attribute or scope.")
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
data/lib/next_page/exceptions.rb
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module NextPage
|
|
4
|
-
module Sort
|
|
5
|
-
# = Name Evaluator
|
|
6
|
-
#
|
|
7
|
-
# Determines if a name represents an attribute or a scope, provides mapping if the scope requires a prefix or
|
|
8
|
-
# suffix. Scope is checked first, allowing it to override an attribute of the same name. For scopes, method
|
|
9
|
-
# <tt>directional_scope?</tt> checks to see if the scope is a class method that accepts one parameter; if so, then
|
|
10
|
-
# the scope will be invoked with the direction.
|
|
11
|
-
class NameEvaluator
|
|
12
|
-
attr_reader :scope_name
|
|
13
|
-
|
|
14
|
-
def initialize(model, name)
|
|
15
|
-
@model = model
|
|
16
|
-
@name = name.to_s
|
|
17
|
-
evaluate_for_scope
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def scope?
|
|
21
|
-
@scope_name.present?
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# true when scope is class method with one parameter
|
|
25
|
-
def directional_scope?
|
|
26
|
-
return false unless scope?
|
|
27
|
-
|
|
28
|
-
@model.method(@scope_name).arity == 1
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def valid_attribute_name?
|
|
32
|
-
@model.attribute_names.include?(@name)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def evaluate_for_scope
|
|
38
|
-
assign_scope(@name) || assign_prefixed_scope || assign_suffixed_scope
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def assign_scope(potential_scope)
|
|
42
|
-
return if @model.dangerous_class_method?(potential_scope) || !@model.respond_to?(potential_scope)
|
|
43
|
-
|
|
44
|
-
@scope_name = potential_scope
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def assign_prefixed_scope
|
|
48
|
-
return unless NextPage.configuration.sort_scope_prefix?
|
|
49
|
-
|
|
50
|
-
assign_scope("#{NextPage.configuration.sort_scope_prefix}#{@name}")
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def assign_suffixed_scope
|
|
54
|
-
return unless NextPage.configuration.sort_scope_suffix?
|
|
55
|
-
|
|
56
|
-
assign_scope("#{@name}#{NextPage.configuration.sort_scope_suffix}")
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module NextPage
|
|
4
|
-
module Sort
|
|
5
|
-
# = Segment Parser
|
|
6
|
-
#
|
|
7
|
-
# Parses each sort segment to provide direction, associations, and name.
|
|
8
|
-
class SegmentParser
|
|
9
|
-
attr_reader :direction, :associations, :name
|
|
10
|
-
|
|
11
|
-
SEGMENT_REGEX = /(?<sign>[+|-]?)(?<names>.+)/
|
|
12
|
-
|
|
13
|
-
def initialize(segment)
|
|
14
|
-
@segment = segment
|
|
15
|
-
parsed = segment.match SEGMENT_REGEX
|
|
16
|
-
@direction = parsed['sign'] == '-' ? 'desc' : 'asc'
|
|
17
|
-
*@associations, @name = *parsed['names'].split('.')
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def to_s
|
|
21
|
-
@segment
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def attribute_with_direction
|
|
25
|
-
{ @name => @direction }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def nested?
|
|
29
|
-
@associations.present?
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module NextPage
|
|
4
|
-
module Sort
|
|
5
|
-
# = Sort Builder
|
|
6
|
-
class SortBuilder
|
|
7
|
-
def initialize(model)
|
|
8
|
-
@model = model
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# TODO: support passing direction to scope
|
|
12
|
-
def build(segment)
|
|
13
|
-
@parser = NextPage::Sort::SegmentParser.new(segment)
|
|
14
|
-
|
|
15
|
-
if @parser.nested?
|
|
16
|
-
build_nested
|
|
17
|
-
else
|
|
18
|
-
build_non_nested
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def build_nested
|
|
25
|
-
sort_model = dig_association_model(@parser.associations)
|
|
26
|
-
joins = build_joins(@parser.associations)
|
|
27
|
-
evaluator = NextPage::Sort::NameEvaluator.new(sort_model, @parser.name)
|
|
28
|
-
|
|
29
|
-
if evaluator.scope?
|
|
30
|
-
build_nested_scope_sort(sort_model, joins, evaluator)
|
|
31
|
-
elsif evaluator.valid_attribute_name?
|
|
32
|
-
nested_attribute_sort(sort_model, joins, @parser.attribute_with_direction)
|
|
33
|
-
else
|
|
34
|
-
raise NextPage::Exceptions::InvalidSortParameter, @parser
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def build_nested_scope_sort(sort_model, joins, evaluator)
|
|
39
|
-
if evaluator.directional_scope?
|
|
40
|
-
nested_directional_scope_sort(sort_model, joins, evaluator.scope_name, @parser.direction)
|
|
41
|
-
else
|
|
42
|
-
nested_scope_sort(sort_model, joins, evaluator.scope_name)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def nested_attribute_sort(model, joins, attribute_with_direction)
|
|
47
|
-
->(query) { query.joins(joins).merge(model.order(attribute_with_direction)) }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def nested_scope_sort(model, joins, scope_name)
|
|
51
|
-
->(query) { query.joins(joins).merge(model.public_send(scope_name)) }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def nested_directional_scope_sort(model, joins, scope_name, direction)
|
|
55
|
-
->(query) { query.joins(joins).merge(model.public_send(scope_name, direction)) }
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def build_non_nested
|
|
59
|
-
evaluator = NextPage::Sort::NameEvaluator.new(@model, @parser.name)
|
|
60
|
-
|
|
61
|
-
if evaluator.scope?
|
|
62
|
-
build_non_nested_scope_sort(evaluator)
|
|
63
|
-
elsif evaluator.valid_attribute_name?
|
|
64
|
-
attribute_sort(@parser.attribute_with_direction)
|
|
65
|
-
else
|
|
66
|
-
raise NextPage::Exceptions::InvalidSortParameter, @parser
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def build_non_nested_scope_sort(evaluator)
|
|
71
|
-
if evaluator.directional_scope?
|
|
72
|
-
directional_scope_sort(evaluator.scope_name, @parser.direction)
|
|
73
|
-
else
|
|
74
|
-
scope_sort(evaluator.scope_name)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def attribute_sort(attribute_with_direction)
|
|
79
|
-
->(query) { query.order(attribute_with_direction) }
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def scope_sort(scope_name)
|
|
83
|
-
->(query) { query.send(scope_name) }
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def directional_scope_sort(scope_name, direction)
|
|
87
|
-
->(query) { query.send(scope_name, direction) }
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# traverse nested associations to find last association's model
|
|
91
|
-
def dig_association_model(associations)
|
|
92
|
-
associations.reduce(@model) do |model, association_name|
|
|
93
|
-
association = model.reflect_on_association(association_name)
|
|
94
|
-
raise NextPage::Exceptions::InvalidNestedSort.new(model, association_name) if association.nil?
|
|
95
|
-
|
|
96
|
-
association.klass
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# transform associations array to nested hash
|
|
101
|
-
# ['team'] => [:team]
|
|
102
|
-
# ['team', 'coach'] => { team: :coach }
|
|
103
|
-
def build_joins(associations)
|
|
104
|
-
associations.map(&:to_sym)
|
|
105
|
-
.reverse
|
|
106
|
-
.reduce { |memo, association| memo.nil? ? association.to_sym : { association => memo } }
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# TODO: consider prefix / suffix
|
|
110
|
-
def named_scope(sort_model)
|
|
111
|
-
# checking dangerous_class_method? excludes any names that cannot be scope names, such as "name"
|
|
112
|
-
return unless sort_model.respond_to?(@parser.name) && !sort_model.dangerous_class_method?(@parser.name)
|
|
113
|
-
|
|
114
|
-
@parser.name
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
end
|
data/lib/next_page/sorter.rb
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'next_page/sort/name_evaluator'
|
|
4
|
-
require 'next_page/sort/segment_parser'
|
|
5
|
-
require 'next_page/sort/sort_builder'
|
|
6
|
-
|
|
7
|
-
module NextPage
|
|
8
|
-
# = Sorter
|
|
9
|
-
#
|
|
10
|
-
# Class Sorter reads the sort parameter and applies the related ordering. Results for each parameter string are
|
|
11
|
-
# cached so evaluation only occurs once.
|
|
12
|
-
class Sorter
|
|
13
|
-
# Initializes a new sorter. The given model is used to validate sort attributes as well as build nested sorts.
|
|
14
|
-
def initialize(model)
|
|
15
|
-
@model = model
|
|
16
|
-
@cache = Hash.new { |hash, key| hash[key] = build_sort(key) }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Adds sorting to given query based upon the param. Returns a new query; the existing query is NOT modified.
|
|
20
|
-
#
|
|
21
|
-
# The +query+ parameter is an ActiveRecord arel or model.
|
|
22
|
-
#
|
|
23
|
-
# The +sort_fields+ parameter is a string that conforms to the JSON-API specification for sorting fields:
|
|
24
|
-
# https://jsonapi.org/format/#fetching-sorting
|
|
25
|
-
def sort(query, sort_fields)
|
|
26
|
-
return from_array(query, sort_fields.split(',')) if sort_fields.include?(',')
|
|
27
|
-
return from_array(query, sort_fields) if sort_fields.is_a? Array
|
|
28
|
-
|
|
29
|
-
apply_sort(query, sort_fields)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def apply_sort(query, key)
|
|
35
|
-
@cache[key].call(query)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def from_array(query, param)
|
|
39
|
-
param.reduce(query) { |memo, key| apply_sort(memo, key) }
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# returns a lambda that applies the appropriate sort, either from a scope, nested attribute, or attribute
|
|
43
|
-
def build_sort(key)
|
|
44
|
-
ActiveSupport::Notifications.instrument('build_sort.next_page', { key: key }) do
|
|
45
|
-
NextPage::Sort::SortBuilder.new(@model).build(key)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|