next_page 0.1.8 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3a633fa748d4bdcff2576c514993b13373a95d999680ef06ef0439ba9f849c3
4
- data.tar.gz: 58f4616e60f4fe84583a03efe134eac635cf19fee0d5da85c99d9790203cdfc3
3
+ metadata.gz: 38c9e0beeee5d1e9d2e9a4a85e25fdf5011c6d9116dcb35ffdaaddfa795a3cca
4
+ data.tar.gz: 408c859f1330a3ce2a577d43f8af4bbf390031d4a5aca33c1a40f7d2ba29465b
5
5
  SHA512:
6
- metadata.gz: d91211f3d61c31c98ec05b8512fc0a277ad13490f654c272b5ec77fd1539cc83f6c17bd87169b18a4d66b8a713237da6858543cda2d45bf6041e3c145c4ecd20
7
- data.tar.gz: 9617247dbad11b9ef5ef083f489728c565a901e632d77a59c184d25ae0ea48d3e55cf4dbd1e9f30a8e1d7c089dd7616822f786b5bebb21d245ea9890364a3fea
6
+ metadata.gz: b2ef11040267561b0daf6174b766c374b998a3effe8c2de9e76b13980386d98bb3b4562e2858a3e70a700a2d45154f6dcd381951dbc997f20af1c99d9435b98e
7
+ data.tar.gz: 7267d87c88104d59d007b6b986fd398853f2591a2b87c25293986fa0fd5559dd7bc08bca4b0a33683f34656165ac3b6490a86d86c7d0f2d647950d558da6dc56
data/README.md CHANGED
@@ -1,45 +1,75 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/next_page.svg)](https://badge.fury.io/rb/next_page)
2
+ [![RSpec](https://github.com/RockSolt/next_page/actions/workflows/rspec.yml/badge.svg)](https://github.com/RockSolt/next_page/actions/workflows/rspec.yml)
2
3
  [![RuboCop](https://github.com/RockSolt/next_page/workflows/RuboCop/badge.svg)](https://github.com/RockSolt/next_page/actions?query=workflow%3ARuboCop)
3
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/0efe1a9b66a0bf161536/maintainability)](https://codeclimate.com/github/RockSolt/next_page/maintainability)
4
5
 
5
6
  # NextPage
6
- Basic pagination for Rails controllers.
7
7
 
8
- ## Usage
9
- Module Pagination provides pagination for index methods. It assigns a limit and offset to the resource query and extends the relation with mixin NextPage::PaginationAttributes to provide helper methods for generating links.
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.
10
11
 
11
- ### Include the Module
12
- Add an include statement for the module into any controller that needs pagination:
12
+ No more, no less.
13
13
 
14
- ```ruby
15
- include NextPage::Pagination
16
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)
17
34
 
18
- There are two ways to paginate: using a before filter or by calling `paginate_resource` explicitly.
35
+ ## Getting Started
19
36
 
20
- ### Before Filter
21
- Here's an example of using the before filter in a controller:
37
+ This gem requires Rails 7.1+ and works with ActiveRecord.
38
+
39
+ ### Installation
40
+
41
+ Add this line to your application's Gemfile:
22
42
 
23
43
  ```ruby
24
- before_action :apply_next_page_pagination, only: :index
44
+ gem 'next_page'
25
45
  ```
26
46
 
27
- This entry point uses the following conventions to apply pagination:
28
- - the name of the instance variable is the sames as the component (for example PhotosController -> @photos)
29
- - the name of the models is the controller name singularized (for example PhotosController -> Photo)
47
+ And then execute:
48
+ ```bash
49
+ $ bundle
50
+ ```
30
51
 
31
- Either can be overridden by calling method `paginate_with` in the controller. The two override options are
32
- `instance_variable_name` and `model_class`. For example, if the PhotosController used the model Picture and the
33
- instance variable name @photographs, the controller declares it as follows:
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):
34
66
 
35
67
  ```ruby
36
- paginate_with instance_variable_name: :photographs, model_class: 'Picture'
68
+ include NextPage::Pagination
37
69
  ```
38
70
 
39
- If the before filter is used, it will populate an instance variable. The action should NOT reset the variable, as
40
- that removes pagination.
71
+ ### Invoking Pagination
41
72
 
42
- ### Invoking Pagination Directly
43
73
  To paginate a resource pass the resource into method `paginate_resource` then store the return value back in the
44
74
  resource:
45
75
 
@@ -47,98 +77,82 @@ resource:
47
77
  @photos = paginate_resource(@photos)
48
78
  ```
49
79
 
50
- ### Sorting
51
- 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.
52
-
53
- /photos?sort=-created_at
54
- /photos?sort=location,-created_by
55
- /photos?sort[]=location&photos[]=-created_by
80
+ The resource is decorated, so this needs to be the last step before rendering.
56
81
 
57
- The default sort order is primary key descending. It can be overridden by using the `default_sort` option of `paginate_with`. Use a string formatted just as url parameter would be formatted.
82
+ ### Link Helpers
58
83
 
59
- ```ruby
60
- paginate_with default_sort: '-created_at'
61
- ```
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
62
91
 
63
- #### Nested Sorts
92
+ The `previous_page` and `last_page` readers will return `nil` on the first and last page, respectively.
64
93
 
65
- Nested attributes and scopes can be indicated by providing the association names separated by periods.
94
+ #### Count Query
66
95
 
67
- /photos?sort=user.name
68
- /photos?sort=-user.address.state
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
69
99
 
70
- #### Directional Scope Sorts
100
+ ### Request Parameters
71
101
 
72
- In order to use directions (`+` or `-`) with a scope, the scope must be defined as a class method and take a single parameter. The scope will receive either `'asc'` or `'desc'`. Here is an example of a valid directional scope.
102
+ In order to control pagingation, the request should pass the `size` and `number` parameters under the
103
+ `page` key:
73
104
 
74
- ```ruby
75
- def self.status(direction)
76
- order("CASE status WHEN 'new' THEN 1 WHEN 'in progress' THEN 2 ELSE 3 END #{direction}")
77
- end
105
+ ```
106
+ ?page[size]=10&page[number]=2
78
107
  ```
79
108
 
80
- #### Scope Prefix / Suffix
81
-
82
- 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).
83
-
84
- 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
85
110
 
86
- For example, if the backend developer prefers <tt>sort_by_status</tt> then the following configuration can be used:
111
+ The default number of results per page can be overridden by specifying a new default with the call:
87
112
 
88
113
  ```ruby
89
- NextPage.configure do |config|
90
- config.sort_scope_prefix = 'sort_by_'
91
- end
92
- ```
93
- This allows the query parameter to be the following:
114
+ @photos = paginate_resource(@photos, default_limit: 25)
115
+ ```
94
116
 
95
- sort=status
117
+ ## Configuration
96
118
 
119
+ There is one configuration option: `default_per_page`.
97
120
 
98
- ### Default Limit
99
- The default size limit can be overridden with the `paginate_with` method for either type of paginagion. Pass option
100
- `default_limit` to specify an override:
121
+ The option can be set directly...
101
122
 
102
- ```ruby
103
- paginate_with default_limit: 25
104
- ```
123
+ `NextPage.configuration.default_per_page = 25`
105
124
 
106
- All the options can be mixed and matches when calling `paginate_with`:
125
+ ...or the configuration can be yielded:
107
126
 
108
- ```ruby
109
- paginate_with model_class: 'Photo', default_limit: 12
110
- paginate_with default_limit: 12, instance_variable_name: 'data'
127
+ ```
128
+ NextPage.configure do |config|
129
+ config.default_per_page = 25
130
+ end
111
131
  ```
112
132
 
113
- ### Link Helpers
114
- 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'`):
115
- - current_page
116
- - next_page
117
- - total_pages
118
- - per_page
133
+ **Results per Page**
119
134
 
120
- #### Count Query
121
- 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:
122
- - provide a count_query that can resolve the attributes
123
- - 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.
124
136
 
137
+ ## Contribute
125
138
 
126
- ## Installation
127
- Add this line to your application's Gemfile:
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.
128
144
 
129
- ```ruby
130
- gem 'next_page'
131
- ```
145
+ ### Running Tests
132
146
 
133
- And then execute:
134
- ```bash
135
- $ bundle
136
- ```
147
+ Tests are written in RSpec and the dummy app uses a docker 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.
137
150
 
138
- Or install it yourself as:
139
151
  ```bash
140
- $ gem install next_page
152
+ bundle exec appraisal install
153
+ bundle exec appraisal rspec
141
154
  ```
142
155
 
143
156
  ## License
157
+
144
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
- # = Configuration
4
+ # # Configuration
5
5
  #
6
6
  # Class Configuration stores the following settings:
7
- # - sort_scope_prefix
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
- attr_reader :sort_scope_prefix, :sort_scope_suffix
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 sort_scope_suffix?
35
- @sort_scope_suffix.present?
11
+ def initialize
12
+ @default_per_page = 12
36
13
  end
37
14
  end
38
15
  end
@@ -1,80 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NextPage
4
- # = Pagination
4
+ # # Pagination
5
5
  #
6
- # Module Pagination provides pagination for index methods. It assigns a limit and offset
6
+ # Module Pagination provides pagination for ActiveRecord queries. It assigns a limit and offset
7
7
  # to the resource query and extends the relation with mixin NextPage::PaginationAttributes.
8
8
  #
9
- # There are two ways to paginate: using a before filter or by calling `paginate_resource` explicitly.
10
9
  #
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
10
+ # ## Invoking Pagination
14
11
  #
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
- #
24
- # If the before filter is used, it will populate an instance variable. The action should NOT reset the variable, as
25
- # that removes pagination.
26
- #
27
- # == Invoking Pagination Directly
28
12
  # To paginate a resource pass the resource into method `paginate_resource` then store the return value back in the
29
13
  # resource:
30
14
  #
31
15
  # @photos = paginate_resource(@photos)
32
16
  #
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
17
  module Pagination
44
18
  extend ActiveSupport::Concern
45
19
 
46
20
  class_methods do
47
- def next_page_paginator #:nodoc:
48
- @next_page_paginator ||= NextPage::Paginator.new(controller_name, controller_path)
21
+ def next_page_paginator # :nodoc:
22
+ @next_page_paginator ||= NextPage::Paginator.new
49
23
  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
24
  end
65
25
 
66
- # Invokes pagination directly, the result must be stored as the resource itself is not modified.
67
- def paginate_resource(resource)
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
26
+ def paginate_resource(resource, default_limit: nil)
27
+ self.class.next_page_paginator.paginate_resource(resource, params.fetch(:page, {}), default_limit)
78
28
  end
79
29
  end
80
30
  end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NextPage
4
- # = Pagination Attributes
4
+ # # Pagination Attributes
5
5
  #
6
- # Module PaginationAttributes adds in methods required for pagination links: previous_page, current_page, next_page,
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
15
  attr_writer :count_query, :current_page, :total_count, :per_page
14
16
 
@@ -1,96 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NextPage
4
- # = Paginator
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
- DEFAULT_LIMIT = 10
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
- def paginate(controller, page_params)
33
- name = "@#{instance_variable_name}"
34
- data = controller.instance_variable_get(name) || model_class.all
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 model_class
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
- def sorter
93
- @sorter ||= NextPage::Sorter.new(model_class)
27
+ data.per_page = per_page
28
+ data.current_page = current_page
94
29
  end
95
30
  end
96
31
  end
@@ -1,3 +1,3 @@
1
1
  module NextPage
2
- VERSION = '0.1.8'
2
+ VERSION = '1.0.0'.freeze
3
3
  end
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,49 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: next_page
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Kummer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-04 00:00:00.000000000 Z
11
+ date: 2025-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '6.0'
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
- version: 6.0.2.2
19
+ version: 7.1.5
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.1.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: appraisal
29
+ requirement: !ruby/object:Gem::Requirement
26
30
  requirements:
27
31
  - - "~>"
28
32
  - !ruby/object:Gem::Version
29
- version: '6.0'
30
- - - ">="
33
+ version: 2.5.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
31
39
  - !ruby/object:Gem::Version
32
- version: 6.0.2.2
40
+ version: 2.5.0
33
41
  - !ruby/object:Gem::Dependency
34
42
  name: guard
35
43
  requirement: !ruby/object:Gem::Requirement
36
44
  requirements:
37
45
  - - "~>"
38
46
  - !ruby/object:Gem::Version
39
- version: '2.16'
47
+ version: '2.19'
40
48
  type: :development
41
49
  prerelease: false
42
50
  version_requirements: !ruby/object:Gem::Requirement
43
51
  requirements:
44
52
  - - "~>"
45
53
  - !ruby/object:Gem::Version
46
- version: '2.16'
54
+ version: '2.19'
47
55
  - !ruby/object:Gem::Dependency
48
56
  name: guard-rspec
49
57
  requirement: !ruby/object:Gem::Requirement
@@ -64,70 +72,70 @@ dependencies:
64
72
  requirements:
65
73
  - - "~>"
66
74
  - !ruby/object:Gem::Version
67
- version: 1.3.0
75
+ version: '1.5'
68
76
  type: :development
69
77
  prerelease: false
70
78
  version_requirements: !ruby/object:Gem::Requirement
71
79
  requirements:
72
80
  - - "~>"
73
81
  - !ruby/object:Gem::Version
74
- version: 1.3.0
82
+ version: '1.5'
75
83
  - !ruby/object:Gem::Dependency
76
84
  name: pg
77
85
  requirement: !ruby/object:Gem::Requirement
78
86
  requirements:
79
87
  - - "~>"
80
88
  - !ruby/object:Gem::Version
81
- version: 1.2.3
89
+ version: 1.5.4
82
90
  type: :development
83
91
  prerelease: false
84
92
  version_requirements: !ruby/object:Gem::Requirement
85
93
  requirements:
86
94
  - - "~>"
87
95
  - !ruby/object:Gem::Version
88
- version: 1.2.3
96
+ version: 1.5.4
89
97
  - !ruby/object:Gem::Dependency
90
98
  name: rspec-rails
91
99
  requirement: !ruby/object:Gem::Requirement
92
100
  requirements:
93
101
  - - "~>"
94
102
  - !ruby/object:Gem::Version
95
- version: '3.9'
103
+ version: '7.0'
96
104
  type: :development
97
105
  prerelease: false
98
106
  version_requirements: !ruby/object:Gem::Requirement
99
107
  requirements:
100
108
  - - "~>"
101
109
  - !ruby/object:Gem::Version
102
- version: '3.9'
110
+ version: '7.0'
103
111
  - !ruby/object:Gem::Dependency
104
112
  name: rubocop
105
113
  requirement: !ruby/object:Gem::Requirement
106
114
  requirements:
107
115
  - - "~>"
108
116
  - !ruby/object:Gem::Version
109
- version: 0.82.0
117
+ version: '1.75'
110
118
  type: :development
111
119
  prerelease: false
112
120
  version_requirements: !ruby/object:Gem::Requirement
113
121
  requirements:
114
122
  - - "~>"
115
123
  - !ruby/object:Gem::Version
116
- version: 0.82.0
124
+ version: '1.75'
117
125
  - !ruby/object:Gem::Dependency
118
126
  name: simplecov
119
127
  requirement: !ruby/object:Gem::Requirement
120
128
  requirements:
121
129
  - - "~>"
122
130
  - !ruby/object:Gem::Version
123
- version: '0.18'
131
+ version: '0.19'
124
132
  type: :development
125
133
  prerelease: false
126
134
  version_requirements: !ruby/object:Gem::Requirement
127
135
  requirements:
128
136
  - - "~>"
129
137
  - !ruby/object:Gem::Version
130
- version: '0.18'
138
+ version: '0.19'
131
139
  description: Provide basic pagination, including page size and number as well as helpers
132
140
  for generating links.
133
141
  email:
@@ -141,18 +149,10 @@ files:
141
149
  - Rakefile
142
150
  - lib/next_page.rb
143
151
  - lib/next_page/configuration.rb
144
- - lib/next_page/exceptions.rb
145
- - lib/next_page/exceptions/invalid_nested_sort.rb
146
- - lib/next_page/exceptions/invalid_sort_parameter.rb
147
152
  - lib/next_page/pagination.rb
148
153
  - lib/next_page/pagination_attributes.rb
149
154
  - lib/next_page/paginator.rb
150
- - lib/next_page/sort/name_evaluator.rb
151
- - lib/next_page/sort/segment_parser.rb
152
- - lib/next_page/sort/sort_builder.rb
153
- - lib/next_page/sorter.rb
154
155
  - lib/next_page/version.rb
155
- - lib/tasks/next_page_tasks.rake
156
156
  homepage: https://github.com/RockSolt/next_page
157
157
  licenses:
158
158
  - MIT
@@ -165,14 +165,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
165
  requirements:
166
166
  - - ">="
167
167
  - !ruby/object:Gem::Version
168
- version: '0'
168
+ version: 3.1.0
169
169
  required_rubygems_version: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - ">="
172
172
  - !ruby/object:Gem::Version
173
173
  version: '0'
174
174
  requirements: []
175
- rubygems_version: 3.0.8
175
+ rubygems_version: 3.5.7
176
176
  signing_key:
177
177
  specification_version: 4
178
178
  summary: Pagination for Rails Controllers
@@ -1,17 +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
- end
11
-
12
- def message
13
- "Invalid nested sort: Unable to find association #{@association} on model #{@model}"
14
- end
15
- end
16
- end
17
- end
@@ -1,16 +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
- end
10
-
11
- def message
12
- "Invalid sort parameter (#{@segment}). Must be an attribute or scope."
13
- end
14
- end
15
- end
16
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module NextPage
4
- module Exceptions
5
- class NextPageError < StandardError
6
- end
7
- end
8
- end
9
-
10
- require 'next_page/exceptions/invalid_nested_sort'
11
- require 'next_page/exceptions/invalid_sort_parameter'
@@ -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>.+)/.freeze
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
@@ -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
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
- # desc "Explaining what the task does"
3
- # task :next_page do
4
- # # Task goes here
5
- # end