jsonapi-query_builder 0.1.8 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f89df14f0f1eed338eb90d946c537cd6230fdbcd6ae964368b00d534660f1a8
4
- data.tar.gz: dd5d5e405d2d2742d72e2fa3699acf111b4ee73a8a7eab9fd264469fde6affb0
3
+ metadata.gz: c0bbac3427e5cd55ee1f3528925a7d5f06817bef89e232a668821450a8cbedf0
4
+ data.tar.gz: 622b993193db4a2644187a99099ed5470380dae1d4f576efccf0465d84a8e7f3
5
5
  SHA512:
6
- metadata.gz: a0b4d6e6cfadf99f3ba968842d5860f663bd34734992757b59cf7c0c9ce0ecae883775c85d6e98f0fc8c394412ae636c712ccba31440a5eb3bd76372ea69cbd0
7
- data.tar.gz: e884df70fc9c0e51489b8be4256ed1192757781b003dd53e1e9a317a3b340a6de910d05289c8e3b3fa9869680873e6405367d9c4b2c929ab34d8f664eedb69c8
6
+ metadata.gz: 5719e6ab6411803bd98cc6aa06ce8471a19c90f713c4b1c014106338bde1b7d7495bcac4be216349524c92489d5873327ba31cc7d4e99c075249ce6db149a54f
7
+ data.tar.gz: 6151a1cd285f11ed2f48c7c140442a3f67ba85f656d601d8a5ad4c2c686c973aad0526340a37271194e9400642db951d071d43993f7d37095fc47861ae89daf0
@@ -0,0 +1,31 @@
1
+ name: lint
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ standardrb:
11
+ runs-on: ubuntu-18.04
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ bundler-cache: true
18
+ - name: standardrb
19
+ run: bundle exec standardrb
20
+
21
+ rubocop-rspec:
22
+ runs-on: ubuntu-18.04
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - uses: ruby/setup-ruby@v1
27
+ with:
28
+ bundler-cache: true
29
+
30
+ - name: rubocop
31
+ run: bundle exec rubocop --only RSpec
@@ -0,0 +1,23 @@
1
+ name: spec
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ rake-spec:
11
+ runs-on: ubuntu-18.04
12
+ strategy:
13
+ matrix:
14
+ ruby: [2.5, 2.6, 2.7, 3.0]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v2
18
+ - uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby }}
21
+ bundler-cache: true
22
+ - name: rake spec
23
+ run: bundle exec rake spec
data/.rubocop.yml CHANGED
@@ -4,5 +4,8 @@ require:
4
4
  RSpec/NestedGroups:
5
5
  Max: 4
6
6
 
7
+ RSpec/MultipleMemoizedHelpers:
8
+ Max: 7
9
+
7
10
  AllCops:
8
11
  NewCops: enable
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.6
1
+ 3.0.2
data/CHANGELOG.md CHANGED
@@ -1,6 +1,36 @@
1
1
  # Change log
2
2
 
3
+ ## 0.3.0 (2021-12-07)
4
+
5
+ ### Enhancements
6
+
7
+ - Add support for proc and object default
8
+ sorts [a167f90](https://github.com/infinum/jsonapi-query_builder/commit/a167f90ca718fe62c0899520cd4c5c859f89035b)
9
+
10
+ ## 0.2.1 (2021-10-04)
11
+
12
+ ### Bugfixes
13
+
14
+ - [#22](https://github.com/infinum/jsonapi-query_builder/pull/22): Bump allowed pagy version.
15
+
16
+ ## 0.2.0 (2021-09-29)
17
+
18
+ ### Enhancements
19
+
20
+ Added support for Kaminari and Keyset pagination strategies in addition to Pagy.
21
+
22
+ - [#21](https://github.com/infinum/jsonapi-query_builder/pull/21): Extract paginators.
23
+
24
+ ## 0.1.9 (2021-05-07)
25
+
26
+ ### Enhancements
27
+
28
+ - [#18](https://github.com/infinum/jsonapi-query_builder/pull/18): Remove Ruby `to` version.
29
+ - [#9](https://github.com/infinum/jsonapi-query_builder/pull/9): added github actions
30
+
3
31
  ## 0.1.8 (2021-01-25)
4
32
 
33
+ ### Enhancements
34
+
5
35
  - [#8](https://github.com/infinum/jsonapi-query_builder/pull/8): add support for ruby 3.0
6
36
 
data/Gemfile.lock CHANGED
@@ -1,39 +1,81 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jsonapi-query_builder (0.1.8)
4
+ jsonapi-query_builder (0.3.0)
5
5
  activerecord (>= 5)
6
- pagy (~> 3.5)
6
+ pagy (>= 3.5)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activemodel (6.1.1)
12
- activesupport (= 6.1.1)
13
- activerecord (6.1.1)
14
- activemodel (= 6.1.1)
15
- activesupport (= 6.1.1)
16
- activesupport (6.1.1)
11
+ actionview (6.1.4.1)
12
+ activesupport (= 6.1.4.1)
13
+ builder (~> 3.1)
14
+ erubi (~> 1.4)
15
+ rails-dom-testing (~> 2.0)
16
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
17
+ activemodel (6.1.4.1)
18
+ activesupport (= 6.1.4.1)
19
+ activerecord (6.1.4.1)
20
+ activemodel (= 6.1.4.1)
21
+ activesupport (= 6.1.4.1)
22
+ activesupport (6.1.4.1)
17
23
  concurrent-ruby (~> 1.0, >= 1.0.2)
18
24
  i18n (>= 1.6, < 2)
19
25
  minitest (>= 5.1)
20
26
  tzinfo (~> 2.0)
21
27
  zeitwerk (~> 2.3)
22
- ast (2.4.1)
23
- concurrent-ruby (1.1.7)
28
+ ast (2.4.2)
29
+ builder (3.2.4)
30
+ bundler-audit (0.9.0.1)
31
+ bundler (>= 1.2.0, < 3)
32
+ thor (~> 1.0)
33
+ coderay (1.1.3)
34
+ concurrent-ruby (1.1.9)
35
+ crass (1.0.6)
24
36
  diff-lcs (1.4.4)
25
- i18n (1.8.7)
37
+ erubi (1.10.0)
38
+ i18n (1.8.10)
26
39
  concurrent-ruby (~> 1.0)
27
- lefthook (0.7.2)
28
- minitest (5.14.3)
29
- pagy (3.10.0)
30
- parallel (1.20.1)
31
- parser (3.0.0.0)
40
+ kaminari (1.2.1)
41
+ activesupport (>= 4.1.0)
42
+ kaminari-actionview (= 1.2.1)
43
+ kaminari-activerecord (= 1.2.1)
44
+ kaminari-core (= 1.2.1)
45
+ kaminari-actionview (1.2.1)
46
+ actionview
47
+ kaminari-core (= 1.2.1)
48
+ kaminari-activerecord (1.2.1)
49
+ activerecord
50
+ kaminari-core (= 1.2.1)
51
+ kaminari-core (1.2.1)
52
+ lefthook (0.7.6)
53
+ loofah (2.12.0)
54
+ crass (~> 1.0.2)
55
+ nokogiri (>= 1.5.9)
56
+ method_source (1.0.0)
57
+ mini_portile2 (2.6.1)
58
+ minitest (5.14.4)
59
+ nokogiri (1.12.5)
60
+ mini_portile2 (~> 2.6.1)
61
+ racc (~> 1.4)
62
+ pagy (3.11.0)
63
+ parallel (1.21.0)
64
+ parser (3.0.2.0)
32
65
  ast (~> 2.4.1)
66
+ pry (0.14.1)
67
+ coderay (~> 1.1)
68
+ method_source (~> 1.0)
69
+ racc (1.5.2)
70
+ rails-dom-testing (2.0.3)
71
+ activesupport (>= 4.2.0)
72
+ nokogiri (>= 1.6)
73
+ rails-html-sanitizer (1.4.2)
74
+ loofah (~> 2.3)
33
75
  rainbow (3.0.0)
34
- rake (13.0.3)
35
- regexp_parser (2.0.3)
36
- rexml (3.2.4)
76
+ rake (13.0.6)
77
+ regexp_parser (2.1.1)
78
+ rexml (3.2.5)
37
79
  rspec (3.10.0)
38
80
  rspec-core (~> 3.10.0)
39
81
  rspec-expectations (~> 3.10.0)
@@ -43,50 +85,56 @@ GEM
43
85
  rspec-expectations (3.10.1)
44
86
  diff-lcs (>= 1.2.0, < 2.0)
45
87
  rspec-support (~> 3.10.0)
46
- rspec-mocks (3.10.1)
88
+ rspec-mocks (3.10.2)
47
89
  diff-lcs (>= 1.2.0, < 2.0)
48
90
  rspec-support (~> 3.10.0)
49
- rspec-support (3.10.1)
50
- rubocop (1.7.0)
91
+ rspec-support (3.10.2)
92
+ rubocop (1.20.0)
51
93
  parallel (~> 1.10)
52
- parser (>= 2.7.1.5)
94
+ parser (>= 3.0.0.0)
53
95
  rainbow (>= 2.2.2, < 4.0)
54
96
  regexp_parser (>= 1.8, < 3.0)
55
97
  rexml
56
- rubocop-ast (>= 1.2.0, < 2.0)
98
+ rubocop-ast (>= 1.9.1, < 2.0)
57
99
  ruby-progressbar (~> 1.7)
58
- unicode-display_width (>= 1.4.0, < 2.0)
59
- rubocop-ast (1.4.0)
60
- parser (>= 2.7.1.5)
61
- rubocop-performance (1.9.2)
62
- rubocop (>= 0.90.0, < 2.0)
100
+ unicode-display_width (>= 1.4.0, < 3.0)
101
+ rubocop-ast (1.12.0)
102
+ parser (>= 3.0.1.1)
103
+ rubocop-performance (1.11.5)
104
+ rubocop (>= 1.7.0, < 2.0)
63
105
  rubocop-ast (>= 0.4.0)
64
- rubocop-rspec (2.1.0)
65
- rubocop (~> 1.0)
66
- rubocop-ast (>= 1.1.0)
106
+ rubocop-rspec (2.5.0)
107
+ rubocop (~> 1.19)
67
108
  ruby-progressbar (1.11.0)
68
- standard (0.11.0)
69
- rubocop (= 1.7.0)
70
- rubocop-performance (= 1.9.2)
109
+ sqlite3 (1.4.2)
110
+ standard (1.3.0)
111
+ rubocop (= 1.20.0)
112
+ rubocop-performance (= 1.11.5)
71
113
  standardrb (1.0.0)
72
114
  standard
115
+ thor (1.1.0)
73
116
  tzinfo (2.0.4)
74
117
  concurrent-ruby (~> 1.0)
75
- unicode-display_width (1.7.0)
118
+ unicode-display_width (2.1.0)
76
119
  zeitwerk (2.4.2)
77
120
 
78
121
  PLATFORMS
79
122
  ruby
80
123
 
81
124
  DEPENDENCIES
125
+ activerecord
82
126
  bundler (~> 2.0)
127
+ bundler-audit
83
128
  jsonapi-query_builder!
129
+ kaminari (~> 1.2)
84
130
  lefthook
131
+ pry
85
132
  rake (~> 13.0)
86
133
  rspec (~> 3.0)
87
134
  rubocop-rspec
135
+ sqlite3
88
136
  standard
89
137
  standardrb
90
138
 
91
139
  BUNDLED WITH
92
- 2.1.4
140
+ 2.2.22
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # Jsonapi::QueryBuilder
1
+ # Jsonapi::QueryBuilder ![lint](https://github.com/infinum/jsonapi-query_builder/workflows/lint/badge.svg)![spec](https://github.com/infinum/jsonapi-query_builder/workflows/spec/badge.svg)
2
2
 
3
3
  `Jsonapi::QueryBuilder` serves the purpose of adding the json api query related SQL conditions to the already scoped
4
- collection, usually used in controller index actions.
4
+ collection, usually used in controller index actions.
5
5
 
6
6
  With the query builder we can easily define logic for query filters, attributes by which we can sort, and delegate
7
7
  pagination parameters to the underlying paginator. Included relationships are automatically included via the
@@ -27,6 +27,9 @@ Or install it yourself as:
27
27
 
28
28
  ```ruby
29
29
  class UserQuery < Jsonapi::QueryBuilder::BaseQuery
30
+ ## pagination
31
+ paginator Jsonapi::QueryBuilder::Paginator::Pagy # default paginator
32
+
30
33
  ## sorting
31
34
  default_sort created_at: :desc
32
35
  sorts_by :last_name
@@ -51,63 +54,112 @@ end
51
54
  ```
52
55
 
53
56
  The query class is initialized using a collection and query parameters. Since query parameters are referenced explicitly
54
- we can pass them as an unsafe hash. `Jsonapi::QueryBuilder::BaseQuery` should not be responsible for scoping records based on
55
- current user permissions, or for any other type of scoping. It's only responsibility is to support the `json:api`
56
- querying. Use `pundit` or similar for policy scoping, custom query objects for other scoping, and then pass the scoped
57
- collection to the `Jsonapi::QueryBuilder::BaseQuery` object.
57
+ we can pass them as an unsafe hash. `Jsonapi::QueryBuilder::BaseQuery` should not be responsible for scoping records
58
+ based on current user permissions, or for any other type of scoping. It's only responsibility is to support
59
+ the `json:api` querying. Use `pundit` or similar for policy scoping, custom query objects for other scoping, and then
60
+ pass the scoped collection to the `Jsonapi::QueryBuilder::BaseQuery` object.
61
+
62
+ ### Pagination
63
+
64
+ Pagination support is configurable using the `paginator` method to define the paginator. It defaults to the `Pagy`
65
+ paginator, a lightweight and fast paginator. Other paginators currently supported are `Kaminari` and an implementation
66
+ of keyset pagination. Before using these paginators we need to explicitly require the gems in our Gemfile and the
67
+ paginator file in question. Additionally one can implement it's own paginator by inheriting
68
+ from `Jsonapi::QueryBuilder::Paginator::BasePaginator`. The minimum required implementation is a `#paginate` method that
69
+ receives page params and returns a page of the collection. It can return the pagination details as the second item of
70
+ the returned array, that can be used in the serializer for pagination metadata.
71
+
72
+ #### Using the Kaminari Paginator
73
+
74
+ ```ruby
75
+ require "jsonapi/query_builder/paginator/kaminari"
76
+
77
+ paginator Jsonapi::QueryBuilder::Paginator::Kaminari
78
+ ```
79
+
80
+ #### Using the Keyset Paginator
81
+
82
+ ```ruby
83
+ require "jsonapi/query_builder/paginator/keyset"
84
+
85
+ paginator Jsonapi::QueryBuilder::Paginator::Keyset
86
+ ```
58
87
 
59
88
  ### Sorting
89
+
60
90
  #### Ensuring deterministic results
91
+
61
92
  Sorting has a fallback to an unique attribute which defaults to the `id` attribute. This ensures deterministic paginated
62
93
  collection responses. You can override the `unique_sort_attribute` in the query object.
94
+
63
95
  ```ruby
64
96
  # set the unique sort attribute
65
97
  unique_sort_attribute :email
66
98
  # use compound unique sort attributes
67
99
  unique_sort_attributes :created_at, :email
68
100
  ````
101
+
69
102
  #### Default sort options
103
+
70
104
  The `default_sort` can be set to sort by any field like `created_at` timestamp or similar. It is only used if no sort
71
105
  parameter is set, unlike the `unique_sort_attribute` which is always appended as the last sort attribute. The parameters
72
- are passed directly to the underlying active record relation, so the usual ordering options are possible.
106
+ are passed directly to the underlying active record relation, so the usual ordering options are possible. It is also
107
+ possible to define the default sort with a lambda or by passing a sort object.
108
+
73
109
  ```ruby
110
+ default_sort :created_at
111
+ # or
74
112
  default_sort created_at: :desc
113
+ # or
114
+ default_sort ->(collection) { collection.order(created_at: :desc) }
115
+ # or
116
+ default_sort SortObject
75
117
  ```
118
+
76
119
  #### Enabling simple sorting for attributes
120
+
77
121
  `sorts_by` denotes which attributes can be used for sorting. Sorting parameters are usually parsed from the
78
122
  `json:api` sort query parameter in the order they are given. So `sort=-first_name,email` would translate to
79
- `{ first_name: :desc, email: :asc }`
123
+ `{ first_name: :desc, email: :asc }`
124
+
80
125
  ```ruby
81
126
  sorts_by :first_name
82
127
  sorts_by :email
83
128
  ```
129
+
84
130
  #### Sorting with lambdas
85
- `sorts_by` also supports passing a lambda to implement a custom order or reorder function. The parameters passed to the
131
+
132
+ `sorts_by` also supports passing a lambda to implement a custom order or reorder function. The parameters passed to the
86
133
  lamdba are collection and the direction of the order, which is either `:desc` or `:asc`.
134
+
87
135
  ```ruby
88
136
  sorts_by :first_name, ->(collection, direction) { collection.order(name: direction) }
89
137
  ```
90
138
 
91
139
  #### Sorting with sort classes
92
- But since we're devout followers of the SOLID principles, we can define a sort class that responds to
93
- `#results` method, which returns the sorted collection. Under the hood the sort class is initialized with
94
- the current scope and the direction parameter.
140
+
141
+ But since we're devout followers of the SOLID principles, we can define a sort class that responds to `#results` method,
142
+ which returns the sorted collection. Under the hood the sort class is initialized with the current scope and the
143
+ direction parameter.
95
144
 
96
145
  ### Filtering
97
146
 
98
147
  #### Simple exact match filters
148
+
99
149
  ```ruby
100
150
  filters_by :first_name
101
151
  # => collection.where(first_name: params.dig(:filter, :first_name)) if params.dig(:filter, :first_name).present?
102
152
  ```
103
153
 
104
154
  #### Lambda as a filter
155
+
105
156
  ```ruby
106
157
  filters_by :email, ->(collection, query) { collection.where('email ilike ?', "%#{query}%") }
107
158
  # => collection.where('email ilike ?', "%#{params.dig(:filter, :email)}%") if params.dig(:filter, :email).present?
108
159
  ```
109
160
 
110
161
  #### Filter classes
162
+
111
163
  We can define a filter class that responds to `#results` method, which returns the filtered collection results. Under
112
164
  the hood the filter class is initialized with the current scope and the query parameter. However, if the object responds
113
165
  to a `call` method it sends the current scope and the query parameter to that instead. This is great if you're using
@@ -116,17 +168,23 @@ query objects for ActiveRecord scopes, you can easily use them to filter with as
116
168
  ```ruby
117
169
  filters_by :type, TypeFilter
118
170
  ```
171
+
119
172
  The filter class could look something like
173
+
120
174
  ```ruby
175
+
121
176
  class TypeFilter < Jsonapi::QueryBuilder::BaseFilter
122
177
  def results
123
178
  collection.where(type: query.split(','))
124
179
  end
125
180
  end
126
181
  ```
182
+
127
183
  Sometimes you need to perform in-memory filtering, for example when database attributes are encrypted. In that case,
128
184
  those filters should be applied last, the order of definition in the query object matters.
185
+
129
186
  ```ruby
187
+
130
188
  class MrnFilter < Jsonapi::QueryBuilder::BaseFilter
131
189
  def results
132
190
  collection.select { |record| /#{query}/.match?(record.mrn) }
@@ -135,26 +193,34 @@ end
135
193
  ```
136
194
 
137
195
  #### Additional Options
196
+
138
197
  You can override the filter query parameter name by passing the `query_parameter` option.
198
+
139
199
  ```ruby
140
200
  filters_by :first_name, query_parameter: 'name'
141
201
  # => collection.where(first_name: params.dig(:filter, :name)) if params.dig(:filter, :name).present?
142
202
  ```
203
+
143
204
  `allow_nil` option changes the filter conditional to allow explicit checks for an attribute null value.
205
+
144
206
  ```ruby
145
207
  filters_by :first_name, allow_nil: true
146
208
  # => collection.where(first_name: params.dig(:filter, :first_name)) if params[:filter]&.key?(:first_name)
147
209
  ```
210
+
148
211
  The conditional when the filter is applied can also be defined explicitly. Note that these options override the
149
212
  `allow_nil` option, as the condition if defined explicitly and you should handle `nil` explicitly as well.
213
+
150
214
  ```ruby
151
215
  filters_by :first_name, if: ->(query) { query.length >= 2 }
152
216
  # => collection.where(first_name: params.dig(:filter, :first_name)) if params.dig(:filter, :first_name) >= 2
153
217
  filters_by :first_name, unless: ->(query) { query.length < 2 }
154
218
  # => collection.where(first_name: params.dig(:filter, :first_name)) unless params.dig(:filter, :first_name) < 2
155
219
  ```
220
+
156
221
  When you're using a filter class you can pass a symbol to the `:if` and `:unless` options which invokes the method on
157
222
  the filter class.
223
+
158
224
  ```ruby
159
225
  filters_by :type, TypeFilter, if: :correct_type?
160
226
  # => type_filter = TypeFilter.new(collection, query); type_filter.results if type_filter.correct_type?
@@ -176,7 +242,6 @@ version, push git commits and tags, and push the `.gem` file to [rubygems.org](h
176
242
 
177
243
  Bug reports and pull requests are welcome on GitHub at https://github.com/infinum/jsonapi-query_builder.
178
244
 
179
-
180
245
  ## License
181
246
 
182
247
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -36,16 +36,21 @@ Gem::Specification.new do |spec|
36
36
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
37
  spec.require_paths = ["lib"]
38
38
 
39
- spec.required_ruby_version = [">= 2.5", "<= 3.0"]
39
+ spec.required_ruby_version = ">= 2.5"
40
40
 
41
41
  spec.add_runtime_dependency "activerecord", ">= 5"
42
- spec.add_runtime_dependency "pagy", "~> 3.5"
42
+ spec.add_runtime_dependency "pagy", ">= 3.5"
43
43
 
44
44
  spec.add_development_dependency "bundler", "~> 2.0"
45
+ spec.add_development_dependency "bundler-audit"
45
46
  spec.add_development_dependency "rake", "~> 13.0"
46
47
  spec.add_development_dependency "rspec", "~> 3.0"
47
48
  spec.add_development_dependency "standardrb"
48
49
  spec.add_development_dependency "standard"
49
50
  spec.add_development_dependency "rubocop-rspec"
50
51
  spec.add_development_dependency "lefthook"
52
+ spec.add_development_dependency "kaminari", "~> 1.2"
53
+ spec.add_development_dependency "activerecord"
54
+ spec.add_development_dependency "sqlite3"
55
+ spec.add_development_dependency "pry"
51
56
  end
data/lefthook.yml CHANGED
@@ -2,13 +2,13 @@ lint:
2
2
  commands: &lint
3
3
  lint-frozen-strings:
4
4
  glob: "*.rb"
5
- run: bundle exec rubocop {staged_files} --only Style/FrozenStringLiteralComment,Layout/EmptyLineAfterMagicComment --format quiet --auto-correct
5
+ run: bundle exec rubocop --only Style/FrozenStringLiteralComment,Layout/EmptyLineAfterMagicComment --format quiet --auto-correct
6
6
 
7
7
  rubocop:
8
8
  commands: &rubocop
9
9
  rubocop-rspec:
10
10
  glob: "spec/*"
11
- run: bundle exec rubocop {staged_files} --only RSpec --format quiet
11
+ run: bundle exec rubocop --only RSpec --format quiet
12
12
 
13
13
  pre-commit:
14
14
  parallel: true
@@ -5,6 +5,8 @@ require "jsonapi/query_builder/mixins/include"
5
5
  require "jsonapi/query_builder/mixins/paginate"
6
6
  require "jsonapi/query_builder/mixins/sort"
7
7
 
8
+ require "jsonapi/query_builder/paginator"
9
+
8
10
  module Jsonapi
9
11
  module QueryBuilder
10
12
  class BaseQuery
@@ -7,7 +7,7 @@ module Jsonapi
7
7
 
8
8
  # @param [ActiveRecord::Relation] collection
9
9
  # @param [Symbol] direction of the ordering, one of :asc or :desc
10
- def initialize(collection, direction)
10
+ def initialize(collection, direction = :asc)
11
11
  @collection = collection
12
12
  @direction = direction
13
13
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsonapi
4
+ module QueryBuilder
5
+ module Errors
6
+ class UnpermittedSortParameters < ArgumentError
7
+ def initialize(unpermitted_parameters)
8
+ super [
9
+ unpermitted_parameters.to_sentence,
10
+ unpermitted_parameters.count == 1 ? "is not a" : "are not",
11
+ "permitted sort attribute".pluralize(unpermitted_parameters.count)
12
+ ].join(" ")
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -4,7 +4,20 @@ module Jsonapi
4
4
  module QueryBuilder
5
5
  module Mixins
6
6
  module Paginate
7
- include Pagy::Backend
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ # Sets the paginator used to page the results. Defaults to Pagy
11
+ #
12
+ # @param [Jsonapi::QueryBuilder::Paginator::BasePaginator] paginator A subclass of BasePaginator
13
+ def paginator(paginator)
14
+ @paginator = paginator
15
+ end
16
+
17
+ def _paginator
18
+ @paginator || Paginator::Pagy
19
+ end
20
+ end
8
21
 
9
22
  attr_reader :pagination_details
10
23
 
@@ -14,9 +27,7 @@ module Jsonapi
14
27
  # @param [Object] page_params Optional explicit pagination params
15
28
  # @return [ActiveRecord::Relation] Paged collection
16
29
  def paginate(collection, page_params = send(:page_params))
17
- @pagination_details, records = pagy collection, page: page_params[:number],
18
- items: page_params[:size],
19
- outset: page_params[:offset]
30
+ records, @pagination_details = self.class._paginator.new(collection).paginate(page_params)
20
31
 
21
32
  records
22
33
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "jsonapi/query_builder/mixins/sort/param"
4
+ require "jsonapi/query_builder/errors/unpermitted_sort_parameters"
4
5
 
5
6
  module Jsonapi
6
7
  module QueryBuilder
@@ -8,8 +9,6 @@ module Jsonapi
8
9
  module Sort
9
10
  extend ActiveSupport::Concern
10
11
 
11
- UnpermittedSortParameters = Class.new ArgumentError
12
-
13
12
  class_methods do
14
13
  attr_reader :_default_sort
15
14
 
@@ -59,7 +58,7 @@ module Jsonapi
59
58
  # @param [ActiveRecord::Relation] collection
60
59
  # @param [Object] sort_params Optional explicit sort params
61
60
  # @return [ActiveRecord::Relation] Sorted relation
62
- # @raise [Jsonapi::QueryBuilder::Mixins::Sort::UnpermittedSortParameters] if not all sort parameters are
61
+ # @raise [Jsonapi::QueryBuilder::Errors::UnpermittedSortParameters] if not all sort parameters are
63
62
  # permitted
64
63
  def sort(collection, sort_params = send(:sort_params))
65
64
  sort_params = Param.deserialize_params(sort_params)
@@ -80,16 +79,12 @@ module Jsonapi
80
79
  unpermitted_parameters = sort_params.map(&:attribute).map(&:to_sym) - self.class.supported_sorts.keys
81
80
  return if unpermitted_parameters.size.zero?
82
81
 
83
- raise UnpermittedSortParameters, [
84
- unpermitted_parameters.to_sentence,
85
- unpermitted_parameters.count == 1 ? "is not a" : "are not",
86
- "permitted sort attribute".pluralize(unpermitted_parameters.count)
87
- ].join(" ")
82
+ raise Errors::UnpermittedSortParameters, unpermitted_parameters
88
83
  end
89
84
 
90
85
  def add_order_attributes(collection, sort_params)
91
86
  return collection if self.class._default_sort.nil? && sort_params.blank?
92
- return collection.order(self.class._default_sort) if sort_params.blank?
87
+ return sort_by_default(collection) if sort_params.blank?
93
88
 
94
89
  sort_params.reduce(collection) do |sorted_collection, sort_param|
95
90
  sort = self.class.supported_sorts.fetch(sort_param.attribute.to_sym)
@@ -102,6 +97,18 @@ module Jsonapi
102
97
  end
103
98
  end
104
99
 
100
+ def sort_by_default(collection)
101
+ default_sort = self.class._default_sort
102
+
103
+ if default_sort.is_a?(Symbol) || default_sort.is_a?(Hash)
104
+ collection.order(default_sort)
105
+ elsif default_sort.respond_to?(:call)
106
+ default_sort.call(collection)
107
+ else
108
+ default_sort.new(collection).results
109
+ end
110
+ end
111
+
105
112
  def add_unique_order_attributes(collection)
106
113
  collection.order(*self.class._unique_sort_attributes)
107
114
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsonapi
4
+ module QueryBuilder
5
+ module Paginator
6
+ class BasePaginator
7
+ attr_reader :collection
8
+
9
+ # @param [ActiveRecord::Relation] collection
10
+ def initialize(collection)
11
+ @collection = collection
12
+ end
13
+
14
+ # @param [Hash] page_params
15
+ # @return [[ActiveRecord::Relation, Hash]] Records and pagination details
16
+ def paginate(page_params)
17
+ raise NotImplementedError, "#{self.class} should implement ##{__method__}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "kaminari"
4
+
5
+ module Jsonapi
6
+ module QueryBuilder
7
+ module Paginator
8
+ class Kaminari < BasePaginator
9
+ def paginate(page_params)
10
+ paged_collection = collection
11
+ .page(page_params[:number])
12
+ .per(page_params[:size])
13
+ .padding(page_params[:offset])
14
+
15
+ [paged_collection, pagination_details(paged_collection, page_params)]
16
+ end
17
+
18
+ private
19
+
20
+ def pagination_details(collection, page_params)
21
+ {
22
+ number: collection.current_page,
23
+ size: collection.limit_value,
24
+ offset: page_params[:offset],
25
+ total: collection.total_count,
26
+ total_pages: collection.total_pages,
27
+ next_page: collection.next_page,
28
+ prev_page: collection.prev_page
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+
5
+ module Jsonapi
6
+ module QueryBuilder
7
+ module Paginator
8
+ class Keyset < BasePaginator
9
+ DEFAULT_DIRECTION = :after
10
+ DEFAULT_LIMIT = 25
11
+
12
+ def paginate(page_params)
13
+ page_params = extract_pagination_params(page_params)
14
+ records = apply_pagination(collection, page_params)
15
+
16
+ [records, page_params]
17
+ end
18
+
19
+ private
20
+
21
+ def extract_pagination_params(params)
22
+ {
23
+ column: params.fetch(:column, nil),
24
+ position: params.fetch(:position, nil),
25
+ direction: params.fetch(:direction, DEFAULT_DIRECTION),
26
+ limit: params.fetch(:limit, DEFAULT_LIMIT)
27
+ }
28
+ end
29
+
30
+ def apply_pagination(collection, pagination_params)
31
+ column = pagination_params[:column]
32
+ position = pagination_params[:position]
33
+ direction = pagination_params[:direction]
34
+ limit = pagination_params[:limit]
35
+
36
+ return collection unless column
37
+
38
+ collection = apply_order(collection, column, direction)
39
+ collection = collection.limit(limit.to_i)
40
+
41
+ return collection unless position
42
+
43
+ apply_filter(collection, column, position, direction)
44
+ end
45
+
46
+ def apply_order(collection, column, direction)
47
+ if direction.to_sym == DEFAULT_DIRECTION
48
+ collection.reorder(collection.arel_table[column].asc)
49
+ else
50
+ collection.reorder(collection.arel_table[column].desc)
51
+ end
52
+ end
53
+
54
+ def apply_filter(collection, column, position, direction)
55
+ if direction.to_sym == DEFAULT_DIRECTION
56
+ collection.where(collection.arel_table[column].gt(position))
57
+ else
58
+ collection.where(collection.arel_table[column].lt(position))
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pagy"
4
+ require "pagy/extras/items"
5
+
6
+ module Jsonapi
7
+ module QueryBuilder
8
+ module Paginator
9
+ class Pagy < BasePaginator
10
+ include ::Pagy::Backend
11
+
12
+ def paginate(page_params)
13
+ @params = {page: page_params}
14
+
15
+ pagination_details, records = pagy collection, page: page_params[:number],
16
+ items: page_params[:size],
17
+ outset: page_params[:offset]
18
+ [records, pagination_details]
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :params
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jsonapi/query_builder/paginator/base_paginator"
4
+ require "jsonapi/query_builder/paginator/pagy"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jsonapi
4
4
  module QueryBuilder
5
- VERSION = "0.1.8"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -4,8 +4,6 @@ require "active_support/concern"
4
4
  require "active_support/core_ext/array/conversions"
5
5
  require "active_support/core_ext/hash/keys"
6
6
  require "active_support/core_ext/string/inflections"
7
- require "pagy"
8
- require "pagy/extras/items"
9
7
 
10
8
  require "jsonapi/query_builder/version"
11
9
  require "jsonapi/query_builder/base_query"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-query_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jure Cindro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-25 00:00:00.000000000 Z
11
+ date: 2021-12-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -28,14 +28,14 @@ dependencies:
28
28
  name: pagy
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3.5'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.5'
41
41
  - !ruby/object:Gem::Dependency
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler-audit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +150,62 @@ dependencies:
136
150
  - - ">="
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: kaminari
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '1.2'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '1.2'
167
+ - !ruby/object:Gem::Dependency
168
+ name: activerecord
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: sqlite3
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: pry
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
139
209
  description: |
140
210
  `Jsonapi::QueryBuilder` serves the purpose of adding the json api query related SQL conditions to the already scoped collection, usually used in controller index actions.
141
211
 
@@ -146,6 +216,8 @@ executables: []
146
216
  extensions: []
147
217
  extra_rdoc_files: []
148
218
  files:
219
+ - ".github/workflows/lint.yml"
220
+ - ".github/workflows/spec.yml"
149
221
  - ".gitignore"
150
222
  - ".rspec"
151
223
  - ".rubocop.yml"
@@ -164,11 +236,17 @@ files:
164
236
  - lib/jsonapi/query_builder/base_filter.rb
165
237
  - lib/jsonapi/query_builder/base_query.rb
166
238
  - lib/jsonapi/query_builder/base_sort.rb
239
+ - lib/jsonapi/query_builder/errors/unpermitted_sort_parameters.rb
167
240
  - lib/jsonapi/query_builder/mixins/filter.rb
168
241
  - lib/jsonapi/query_builder/mixins/include.rb
169
242
  - lib/jsonapi/query_builder/mixins/paginate.rb
170
243
  - lib/jsonapi/query_builder/mixins/sort.rb
171
244
  - lib/jsonapi/query_builder/mixins/sort/param.rb
245
+ - lib/jsonapi/query_builder/paginator.rb
246
+ - lib/jsonapi/query_builder/paginator/base_paginator.rb
247
+ - lib/jsonapi/query_builder/paginator/kaminari.rb
248
+ - lib/jsonapi/query_builder/paginator/keyset.rb
249
+ - lib/jsonapi/query_builder/paginator/pagy.rb
172
250
  - lib/jsonapi/query_builder/version.rb
173
251
  homepage: https://github.com/infinum/jsonapi-query_builder
174
252
  licenses:
@@ -186,16 +264,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
186
264
  - - ">="
187
265
  - !ruby/object:Gem::Version
188
266
  version: '2.5'
189
- - - "<="
190
- - !ruby/object:Gem::Version
191
- version: '3.0'
192
267
  required_rubygems_version: !ruby/object:Gem::Requirement
193
268
  requirements:
194
269
  - - ">="
195
270
  - !ruby/object:Gem::Version
196
271
  version: '0'
197
272
  requirements: []
198
- rubygems_version: 3.0.3
273
+ rubygems_version: 3.2.22
199
274
  signing_key:
200
275
  specification_version: 4
201
276
  summary: Support `json:api` querying with ease!