jsonapi-query_builder 0.2.1 → 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: f47ebd86badc968ef8a79333f49ac496c067ad6a26c6563cca0257c1e8cdeba2
4
- data.tar.gz: ff874753c9a7ba077bf7876a8a598dd01058a0e2f7a2a982dde2fc579342a5d0
3
+ metadata.gz: c0bbac3427e5cd55ee1f3528925a7d5f06817bef89e232a668821450a8cbedf0
4
+ data.tar.gz: 622b993193db4a2644187a99099ed5470380dae1d4f576efccf0465d84a8e7f3
5
5
  SHA512:
6
- metadata.gz: fc7168dad1ea652617381662a8e75cadbb132addb9b78d6af052b15b590d3f031fa143a6508e6f2feb52f82ee9f333892551378fa5a969ac5232bb48ca8b0c8e
7
- data.tar.gz: 6ead187ee07d7d9db557c2010b3d2c992c01d05c888ad6a777716380fa565fc9aff54fb39db54120131bbb544418f789f53cade618c55db86d2aec3d03c08a61
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jsonapi-query_builder (0.2.1)
4
+ jsonapi-query_builder (0.3.0)
5
5
  activerecord (>= 5)
6
6
  pagy (>= 3.5)
7
7
 
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
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
@@ -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 based on
58
- current user permissions, or for any other type of scoping. It's only responsibility is to support the `json:api`
59
- querying. Use `pundit` or similar for policy scoping, custom query objects for other scoping, and then pass the scoped
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
- Additionally one can implement it's own paginator by inheriting from `Jsonapi::QueryBuilder::Paginator::BasePaginator`.
68
- The minimum required implementation is a `#paginate` method that receives page params and returns a page of the
69
- collection. It can return the pagination details as the second item of the returned array, that can be used in the
70
- serializer for pagination metadata.
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
- `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
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
- But since we're devout followers of the SOLID principles, we can define a sort class that responds to
119
- `#results` method, which returns the sorted collection. Under the hood the sort class is initialized with
120
- 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.
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.order(self.class._default_sort) if sort_params.blank?
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jsonapi
4
4
  module QueryBuilder
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  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.2.1
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-10-04 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