jsonapi-query_builder 0.2.1 → 0.3.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/CHANGELOG.md +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +57 -18
- data/lib/jsonapi/query_builder/base_sort.rb +1 -1
- data/lib/jsonapi/query_builder/mixins/sort.rb +13 -1
- data/lib/jsonapi/query_builder/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0bbac3427e5cd55ee1f3528925a7d5f06817bef89e232a668821450a8cbedf0
|
4
|
+
data.tar.gz: 622b993193db4a2644187a99099ed5470380dae1d4f576efccf0465d84a8e7f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5719e6ab6411803bd98cc6aa06ce8471a19c90f713c4b1c014106338bde1b7d7495bcac4be216349524c92489d5873327ba31cc7d4e99c075249ce6db149a54f
|
7
|
+
data.tar.gz: 6151a1cd285f11ed2f48c7c140442a3f67ba85f656d601d8a5ad4c2c686c973aad0526340a37271194e9400642db951d071d43993f7d37095fc47861ae89daf0
|
data/CHANGELOG.md
CHANGED
@@ -1,20 +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
|
+
|
3
10
|
## 0.2.1 (2021-10-04)
|
4
11
|
|
12
|
+
### Bugfixes
|
13
|
+
|
5
14
|
- [#22](https://github.com/infinum/jsonapi-query_builder/pull/22): Bump allowed pagy version.
|
6
15
|
|
7
16
|
## 0.2.0 (2021-09-29)
|
17
|
+
|
18
|
+
### Enhancements
|
19
|
+
|
8
20
|
Added support for Kaminari and Keyset pagination strategies in addition to Pagy.
|
9
21
|
|
10
22
|
- [#21](https://github.com/infinum/jsonapi-query_builder/pull/21): Extract paginators.
|
11
23
|
|
12
24
|
## 0.1.9 (2021-05-07)
|
13
25
|
|
26
|
+
### Enhancements
|
27
|
+
|
14
28
|
- [#18](https://github.com/infinum/jsonapi-query_builder/pull/18): Remove Ruby `to` version.
|
15
29
|
- [#9](https://github.com/infinum/jsonapi-query_builder/pull/9): added github actions
|
16
30
|
|
17
31
|
## 0.1.8 (2021-01-25)
|
18
32
|
|
33
|
+
### Enhancements
|
34
|
+
|
19
35
|
- [#8](https://github.com/infinum/jsonapi-query_builder/pull/8): add support for ruby 3.0
|
20
36
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Jsonapi::QueryBuilder 
|
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
|
@@ -29,7 +29,7 @@ Or install it yourself as:
|
|
29
29
|
class UserQuery < Jsonapi::QueryBuilder::BaseQuery
|
30
30
|
## pagination
|
31
31
|
paginator Jsonapi::QueryBuilder::Paginator::Pagy # default paginator
|
32
|
-
|
32
|
+
|
33
33
|
## sorting
|
34
34
|
default_sort created_at: :desc
|
35
35
|
sorts_by :last_name
|
@@ -54,21 +54,23 @@ end
|
|
54
54
|
```
|
55
55
|
|
56
56
|
The query class is initialized using a collection and query parameters. Since query parameters are referenced explicitly
|
57
|
-
we can pass them as an unsafe hash. `Jsonapi::QueryBuilder::BaseQuery` should not be responsible for scoping records
|
58
|
-
current user permissions, or for any other type of scoping. It's only responsibility is to support
|
59
|
-
querying. Use `pundit` or similar for policy scoping, custom query objects for other scoping, and then
|
60
|
-
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
61
|
|
62
62
|
### Pagination
|
63
|
+
|
63
64
|
Pagination support is configurable using the `paginator` method to define the paginator. It defaults to the `Pagy`
|
64
65
|
paginator, a lightweight and fast paginator. Other paginators currently supported are `Kaminari` and an implementation
|
65
66
|
of keyset pagination. Before using these paginators we need to explicitly require the gems in our Gemfile and the
|
66
|
-
paginator file in question.
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
+
|
71
72
|
#### Using the Kaminari Paginator
|
73
|
+
|
72
74
|
```ruby
|
73
75
|
require "jsonapi/query_builder/paginator/kaminari"
|
74
76
|
|
@@ -76,6 +78,7 @@ paginator Jsonapi::QueryBuilder::Paginator::Kaminari
|
|
76
78
|
```
|
77
79
|
|
78
80
|
#### Using the Keyset Paginator
|
81
|
+
|
79
82
|
```ruby
|
80
83
|
require "jsonapi/query_builder/paginator/keyset"
|
81
84
|
|
@@ -83,57 +86,80 @@ paginator Jsonapi::QueryBuilder::Paginator::Keyset
|
|
83
86
|
```
|
84
87
|
|
85
88
|
### Sorting
|
89
|
+
|
86
90
|
#### Ensuring deterministic results
|
91
|
+
|
87
92
|
Sorting has a fallback to an unique attribute which defaults to the `id` attribute. This ensures deterministic paginated
|
88
93
|
collection responses. You can override the `unique_sort_attribute` in the query object.
|
94
|
+
|
89
95
|
```ruby
|
90
96
|
# set the unique sort attribute
|
91
97
|
unique_sort_attribute :email
|
92
98
|
# use compound unique sort attributes
|
93
99
|
unique_sort_attributes :created_at, :email
|
94
100
|
````
|
101
|
+
|
95
102
|
#### Default sort options
|
103
|
+
|
96
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
|
97
105
|
parameter is set, unlike the `unique_sort_attribute` which is always appended as the last sort attribute. The parameters
|
98
|
-
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
|
+
|
99
109
|
```ruby
|
110
|
+
default_sort :created_at
|
111
|
+
# or
|
100
112
|
default_sort created_at: :desc
|
113
|
+
# or
|
114
|
+
default_sort ->(collection) { collection.order(created_at: :desc) }
|
115
|
+
# or
|
116
|
+
default_sort SortObject
|
101
117
|
```
|
118
|
+
|
102
119
|
#### Enabling simple sorting for attributes
|
120
|
+
|
103
121
|
`sorts_by` denotes which attributes can be used for sorting. Sorting parameters are usually parsed from the
|
104
122
|
`json:api` sort query parameter in the order they are given. So `sort=-first_name,email` would translate to
|
105
|
-
`{ first_name: :desc, email: :asc }`
|
123
|
+
`{ first_name: :desc, email: :asc }`
|
124
|
+
|
106
125
|
```ruby
|
107
126
|
sorts_by :first_name
|
108
127
|
sorts_by :email
|
109
128
|
```
|
129
|
+
|
110
130
|
#### Sorting with lambdas
|
111
|
-
|
131
|
+
|
132
|
+
`sorts_by` also supports passing a lambda to implement a custom order or reorder function. The parameters passed to the
|
112
133
|
lamdba are collection and the direction of the order, which is either `:desc` or `:asc`.
|
134
|
+
|
113
135
|
```ruby
|
114
136
|
sorts_by :first_name, ->(collection, direction) { collection.order(name: direction) }
|
115
137
|
```
|
116
138
|
|
117
139
|
#### Sorting with sort classes
|
118
|
-
|
119
|
-
|
120
|
-
the current scope and the
|
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.
|
121
144
|
|
122
145
|
### Filtering
|
123
146
|
|
124
147
|
#### Simple exact match filters
|
148
|
+
|
125
149
|
```ruby
|
126
150
|
filters_by :first_name
|
127
151
|
# => collection.where(first_name: params.dig(:filter, :first_name)) if params.dig(:filter, :first_name).present?
|
128
152
|
```
|
129
153
|
|
130
154
|
#### Lambda as a filter
|
155
|
+
|
131
156
|
```ruby
|
132
157
|
filters_by :email, ->(collection, query) { collection.where('email ilike ?', "%#{query}%") }
|
133
158
|
# => collection.where('email ilike ?', "%#{params.dig(:filter, :email)}%") if params.dig(:filter, :email).present?
|
134
159
|
```
|
135
160
|
|
136
161
|
#### Filter classes
|
162
|
+
|
137
163
|
We can define a filter class that responds to `#results` method, which returns the filtered collection results. Under
|
138
164
|
the hood the filter class is initialized with the current scope and the query parameter. However, if the object responds
|
139
165
|
to a `call` method it sends the current scope and the query parameter to that instead. This is great if you're using
|
@@ -142,17 +168,23 @@ query objects for ActiveRecord scopes, you can easily use them to filter with as
|
|
142
168
|
```ruby
|
143
169
|
filters_by :type, TypeFilter
|
144
170
|
```
|
171
|
+
|
145
172
|
The filter class could look something like
|
173
|
+
|
146
174
|
```ruby
|
175
|
+
|
147
176
|
class TypeFilter < Jsonapi::QueryBuilder::BaseFilter
|
148
177
|
def results
|
149
178
|
collection.where(type: query.split(','))
|
150
179
|
end
|
151
180
|
end
|
152
181
|
```
|
182
|
+
|
153
183
|
Sometimes you need to perform in-memory filtering, for example when database attributes are encrypted. In that case,
|
154
184
|
those filters should be applied last, the order of definition in the query object matters.
|
185
|
+
|
155
186
|
```ruby
|
187
|
+
|
156
188
|
class MrnFilter < Jsonapi::QueryBuilder::BaseFilter
|
157
189
|
def results
|
158
190
|
collection.select { |record| /#{query}/.match?(record.mrn) }
|
@@ -161,26 +193,34 @@ end
|
|
161
193
|
```
|
162
194
|
|
163
195
|
#### Additional Options
|
196
|
+
|
164
197
|
You can override the filter query parameter name by passing the `query_parameter` option.
|
198
|
+
|
165
199
|
```ruby
|
166
200
|
filters_by :first_name, query_parameter: 'name'
|
167
201
|
# => collection.where(first_name: params.dig(:filter, :name)) if params.dig(:filter, :name).present?
|
168
202
|
```
|
203
|
+
|
169
204
|
`allow_nil` option changes the filter conditional to allow explicit checks for an attribute null value.
|
205
|
+
|
170
206
|
```ruby
|
171
207
|
filters_by :first_name, allow_nil: true
|
172
208
|
# => collection.where(first_name: params.dig(:filter, :first_name)) if params[:filter]&.key?(:first_name)
|
173
209
|
```
|
210
|
+
|
174
211
|
The conditional when the filter is applied can also be defined explicitly. Note that these options override the
|
175
212
|
`allow_nil` option, as the condition if defined explicitly and you should handle `nil` explicitly as well.
|
213
|
+
|
176
214
|
```ruby
|
177
215
|
filters_by :first_name, if: ->(query) { query.length >= 2 }
|
178
216
|
# => collection.where(first_name: params.dig(:filter, :first_name)) if params.dig(:filter, :first_name) >= 2
|
179
217
|
filters_by :first_name, unless: ->(query) { query.length < 2 }
|
180
218
|
# => collection.where(first_name: params.dig(:filter, :first_name)) unless params.dig(:filter, :first_name) < 2
|
181
219
|
```
|
220
|
+
|
182
221
|
When you're using a filter class you can pass a symbol to the `:if` and `:unless` options which invokes the method on
|
183
222
|
the filter class.
|
223
|
+
|
184
224
|
```ruby
|
185
225
|
filters_by :type, TypeFilter, if: :correct_type?
|
186
226
|
# => type_filter = TypeFilter.new(collection, query); type_filter.results if type_filter.correct_type?
|
@@ -202,7 +242,6 @@ version, push git commits and tags, and push the `.gem` file to [rubygems.org](h
|
|
202
242
|
|
203
243
|
Bug reports and pull requests are welcome on GitHub at https://github.com/infinum/jsonapi-query_builder.
|
204
244
|
|
205
|
-
|
206
245
|
## License
|
207
246
|
|
208
247
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -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
|
@@ -84,7 +84,7 @@ module Jsonapi
|
|
84
84
|
|
85
85
|
def add_order_attributes(collection, sort_params)
|
86
86
|
return collection if self.class._default_sort.nil? && sort_params.blank?
|
87
|
-
return collection
|
87
|
+
return sort_by_default(collection) if sort_params.blank?
|
88
88
|
|
89
89
|
sort_params.reduce(collection) do |sorted_collection, sort_param|
|
90
90
|
sort = self.class.supported_sorts.fetch(sort_param.attribute.to_sym)
|
@@ -97,6 +97,18 @@ module Jsonapi
|
|
97
97
|
end
|
98
98
|
end
|
99
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
|
+
|
100
112
|
def add_unique_order_attributes(collection)
|
101
113
|
collection.order(*self.class._unique_sort_attributes)
|
102
114
|
end
|
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.
|
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-
|
11
|
+
date: 2021-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|