graphql-filters 1.1.5 → 1.1.7
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/README.md +168 -1
- data/lib/graphql/filters/error.rb +5 -0
- data/lib/graphql/filters/input_types/fields_comparison_input_type.rb +12 -1
- data/lib/graphql/filters/input_types/string_comparison_input_type.rb +30 -26
- data/lib/graphql/filters/version.rb +1 -1
- data/lib/graphql/filters.rb +1 -0
- metadata +4 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af871adf4f20074267e23317255e81bff397756f724200e340d7d1dc878adb23
|
|
4
|
+
data.tar.gz: eaa5d6246257da87995933a8fd56ef0666a6b4e29babc24e34079fcc786f45c0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b2aa98387e913285068058dde306b188f90a4c921a9939e41d1542e90e725572d9bd6b9b2cc2425dc88d4f3beec84b10a44171041eb79b69472bf816e799875
|
|
7
|
+
data.tar.gz: b4934e3e62c1b4c79fc37da84ad14d879b136934e0615d8030c508b8daf5c338789c941995ee515c42274d9e279e206fb5d6159bc560b39469dc324fedd79193
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
### Bug fixes
|
|
9
9
|
)-->
|
|
10
10
|
|
|
11
|
+
## 1.1.7 2026-07-22
|
|
12
|
+
|
|
13
|
+
### New features
|
|
14
|
+
|
|
15
|
+
- Added `GraphQL::Filters::Error` as the common base for gem domain errors.
|
|
16
|
+
|
|
17
|
+
### Bug fixes
|
|
18
|
+
|
|
19
|
+
- Invalid string match syntax now raises an explicit error `GraphQL::Filters::InputTypes::StringComparisonInputType::InvalidMatchError` for invalid string match filters. instead of returning `nil`; unsupported versions and options no longer raise a generic `RuntimeError`.
|
|
20
|
+
|
|
21
|
+
## 1.1.6 2026-06-03
|
|
22
|
+
|
|
23
|
+
### Bug fixes
|
|
24
|
+
|
|
25
|
+
- Fixed through associations breaking nested filters.
|
|
26
|
+
|
|
11
27
|
## 1.1.5 2025-08-18
|
|
12
28
|
|
|
13
29
|
### Bug fixes
|
data/README.md
CHANGED
|
@@ -92,6 +92,7 @@ query {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
}
|
|
95
96
|
}
|
|
96
97
|
```
|
|
97
98
|
|
|
@@ -99,6 +100,9 @@ Notice that eager loading is outside the scope of this gem, so without anything
|
|
|
99
100
|
|
|
100
101
|
Each input type is generated based on the respective type: scalar and enum types allow for basic comparisons like equality and inclusion, while object types let you build complex queries that mix and match comparisons on their fields. List types let you make `any`, `all`, and `none` queries based on a nested filter. Support for null-checked filters is planned for future development.
|
|
101
102
|
|
|
103
|
+
> [!NOTE]
|
|
104
|
+
> Active Storage classes don't play well with GraphQL Filters. You should disable filters for their respective fields using `filter: false`. A cleaner handling of these cases is planned for future development.
|
|
105
|
+
|
|
102
106
|
### Underlying models
|
|
103
107
|
|
|
104
108
|
GraphQL Filters relies on the assumption that every object type exists on top of an Active Record model. A field of type `PokemonType` will always be resolved to an instance of `Pokemon`, and its fields will match (at least loosely) the attributes of the model. This assumption allows the gem to generate appropriate subqueries for nested filters.
|
|
@@ -155,9 +159,15 @@ The best way to know what comparators you can use with each field is to open the
|
|
|
155
159
|
`matches` is available for the `String` type. Its value is a string in the format `<version>/<pattern>/<options>`.
|
|
156
160
|
|
|
157
161
|
- At the moment, `<version>` can only be `v1`, but extensions are planned for future development.
|
|
158
|
-
- For version `v1`, `<pattern>` will match a `.` with any one character and a `*` with zero or more characters. To match any literal character you can prefix it with
|
|
162
|
+
- For version `v1`, `<pattern>` will match a `.` with any one character and a `*` with zero or more characters. To match any literal character you can prefix it with `\`.
|
|
159
163
|
- For version `v1`, `<options>` can be empty, or be `i`, which will make the match case insensitive.
|
|
160
164
|
|
|
165
|
+
> [!NOTE]
|
|
166
|
+
> This is *not* a regex engine, just a simple wildcard matcher.
|
|
167
|
+
|
|
168
|
+
Malformed match syntax, unsupported pattern versions, and unsupported options raise
|
|
169
|
+
`GraphQL::Filters::InputTypes::StringComparisonInputType::InvalidMatchError`.
|
|
170
|
+
|
|
161
171
|
#### List comparisons
|
|
162
172
|
|
|
163
173
|
`any`, `all`, and `none` are available for any list, take a comparator for the type of the elements of the list, and match, respectively, if at least one, all, or none, of the elements of the list match the nested comparator.
|
|
@@ -236,6 +246,163 @@ field :name, String, null: false, filter: {enabled: false}
|
|
|
236
246
|
|
|
237
247
|
The type the filters for this field need to be based on, if it's different from the field's own type (for example, a field thet returns a connection will need to be filtered based on the connection's node type). Can also be set calling the `filtered_type` on a resolver/mutation if the field is associated with one.
|
|
238
248
|
|
|
249
|
+
### API usage examples
|
|
250
|
+
|
|
251
|
+
To get all pokémon whose main color is blue:
|
|
252
|
+
|
|
253
|
+
```graphql
|
|
254
|
+
query{
|
|
255
|
+
pokemons(
|
|
256
|
+
filter: {
|
|
257
|
+
fields: {
|
|
258
|
+
mainColor: { equals: "blue" }
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
){
|
|
262
|
+
...
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
To get all pokémon whose main color is either blue or red:
|
|
268
|
+
|
|
269
|
+
```graphql
|
|
270
|
+
query{
|
|
271
|
+
pokemons(
|
|
272
|
+
filter: {
|
|
273
|
+
fields: {
|
|
274
|
+
mainColor: { in: ["blue", "red"] }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
){
|
|
278
|
+
...
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
To get all pokémon whose name begins with 's':
|
|
284
|
+
|
|
285
|
+
```graphql
|
|
286
|
+
query{
|
|
287
|
+
pokemons(
|
|
288
|
+
filter: {
|
|
289
|
+
fields: {
|
|
290
|
+
name: { match: "v1/s*/i" }
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
){
|
|
294
|
+
...
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
To get all pokémon whose base friendship is 70 or whose catch rate is more than 100:
|
|
300
|
+
|
|
301
|
+
```graphql
|
|
302
|
+
query{
|
|
303
|
+
pokemons(
|
|
304
|
+
filter: {
|
|
305
|
+
or: [
|
|
306
|
+
{
|
|
307
|
+
fields: {
|
|
308
|
+
baseFriendship: { equals: 70 }
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
{
|
|
312
|
+
fields: {
|
|
313
|
+
catchRate: { greaterThanOrEqualsTo: 100 }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
]
|
|
317
|
+
}
|
|
318
|
+
){
|
|
319
|
+
...
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
To get all pokémon that evolve from Eevee:
|
|
325
|
+
|
|
326
|
+
```graphql
|
|
327
|
+
query{
|
|
328
|
+
pokemons(
|
|
329
|
+
filter: {
|
|
330
|
+
fields: {
|
|
331
|
+
preEvolution: {
|
|
332
|
+
fields: {
|
|
333
|
+
name: { equals: "Eevee" }
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
){
|
|
339
|
+
...
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
To get all pokémon catchable in the Kanto route 1:
|
|
345
|
+
|
|
346
|
+
```graphql
|
|
347
|
+
query{
|
|
348
|
+
pokemons(
|
|
349
|
+
filter: {
|
|
350
|
+
fields: {
|
|
351
|
+
routes: {
|
|
352
|
+
any: {
|
|
353
|
+
fields: {
|
|
354
|
+
name: { equals: "1" }
|
|
355
|
+
region: {
|
|
356
|
+
fields: {
|
|
357
|
+
name: { equals: "Kanto" }
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
){
|
|
366
|
+
...
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
To get all routes in Kanto where you can catch a water type pokémon (this is the example at the top of the page):
|
|
372
|
+
|
|
373
|
+
```graphql
|
|
374
|
+
query {
|
|
375
|
+
routes(
|
|
376
|
+
filter: {
|
|
377
|
+
fields: {
|
|
378
|
+
region: {
|
|
379
|
+
fields: {
|
|
380
|
+
name: { equals: "Kanto" }
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
catchablePokemon: {
|
|
384
|
+
any: {
|
|
385
|
+
fields: {
|
|
386
|
+
pokemon: {
|
|
387
|
+
types: {
|
|
388
|
+
any: {
|
|
389
|
+
fields: {
|
|
390
|
+
name: { equals: "Water"}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
){
|
|
401
|
+
...
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
239
406
|
## Plans for future development
|
|
240
407
|
|
|
241
408
|
- A finer grain support for non-nullable fields.
|
|
@@ -55,7 +55,18 @@ module GraphQL
|
|
|
55
55
|
|
|
56
56
|
scope.and nested_query
|
|
57
57
|
else
|
|
58
|
-
scope.
|
|
58
|
+
reflection = scope.klass.reflect_on_association association_name
|
|
59
|
+
|
|
60
|
+
if reflection.through_reflection?
|
|
61
|
+
through_reflection = reflection.through_reflection
|
|
62
|
+
scope.where(
|
|
63
|
+
through_reflection.name => through_reflection.klass.where(
|
|
64
|
+
reflection.source_reflection.name => nested_query
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
else
|
|
68
|
+
scope.where association_name => nested_query
|
|
69
|
+
end
|
|
59
70
|
end
|
|
60
71
|
end
|
|
61
72
|
end
|
|
@@ -4,6 +4,8 @@ module GraphQL
|
|
|
4
4
|
module Filters
|
|
5
5
|
module InputTypes
|
|
6
6
|
class StringComparisonInputType < BaseScalarComparisonInputType[GraphQL::Types::String]
|
|
7
|
+
class InvalidMatchError < GraphQL::Filters::Error; end
|
|
8
|
+
|
|
7
9
|
argument :match,
|
|
8
10
|
String,
|
|
9
11
|
prepare: lambda { |value, _context|
|
|
@@ -19,43 +21,45 @@ module GraphQL
|
|
|
19
21
|
private
|
|
20
22
|
|
|
21
23
|
def resolve_pattern column_node, expression
|
|
22
|
-
expression.match %r{
|
|
23
|
-
|
|
24
|
+
match_data = expression.match %r{\Av(?<version>\d+)/(?<full_pattern>.*)\z}
|
|
25
|
+
raise InvalidMatchError, 'Invalid match filter syntax. Expected v1/<v1_pattern>.' unless match_data
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
raise InvalidMatchError, 'The only supported version of pattern is v1' if match_data[:version].to_i != 1
|
|
28
|
+
|
|
29
|
+
resolve_v1 column_node, match_data[:full_pattern]
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def resolve_v1 column_node, full_pattern
|
|
30
|
-
full_pattern.match %r{(?<pattern>.*?)/(?<options>.*)}
|
|
31
|
-
|
|
33
|
+
match_data = full_pattern.match %r{\A(?<pattern>.*?)/(?<options>.*)\z}
|
|
34
|
+
raise InvalidMatchError, 'Invalid match filter syntax for version v1. Expected v1/<pattern>/<options>.' unless match_data
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
raise 'The only supported option is \'i\' for case insensitive matching'
|
|
35
|
-
end
|
|
36
|
+
options = match_data[:options].chars.map(&:to_sym)
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
if options.present? && options != [:i]
|
|
39
|
+
raise InvalidMatchError, 'The only supported option is \'i\' for case insensitive matching'
|
|
40
|
+
end
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
pattern = ''
|
|
42
|
+
case_sensitive = !options.include?(:i)
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
characters = match_data[:pattern].chars
|
|
45
|
+
pattern = ''
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ActiveRecord::Base.sanitize_sql_like characters.shift
|
|
48
|
-
when '*'
|
|
49
|
-
'%'
|
|
50
|
-
when '.'
|
|
51
|
-
'_'
|
|
52
|
-
else
|
|
53
|
-
ActiveRecord::Base.sanitize_sql_like char
|
|
54
|
-
end
|
|
55
|
-
end
|
|
47
|
+
while characters.present?
|
|
48
|
+
char = characters.shift
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
pattern << case char
|
|
51
|
+
when '\\'
|
|
52
|
+
ActiveRecord::Base.sanitize_sql_like characters.shift
|
|
53
|
+
when '*'
|
|
54
|
+
'%'
|
|
55
|
+
when '.'
|
|
56
|
+
'_'
|
|
57
|
+
else
|
|
58
|
+
ActiveRecord::Base.sanitize_sql_like char
|
|
59
|
+
end
|
|
58
60
|
end
|
|
61
|
+
|
|
62
|
+
column_node.matches pattern, nil, case_sensitive
|
|
59
63
|
end
|
|
60
64
|
end
|
|
61
65
|
end
|
data/lib/graphql/filters.rb
CHANGED
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql-filters
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Moku S.r.l.
|
|
8
8
|
- Riccardo Agatea
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: exe
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: activerecord
|
|
@@ -112,6 +111,7 @@ files:
|
|
|
112
111
|
- lib/graphql/filters/dsl/graphql/schema/scalar.rb
|
|
113
112
|
- lib/graphql/filters/dsl/graphql/types/numeric.rb
|
|
114
113
|
- lib/graphql/filters/dsl/graphql/types/string.rb
|
|
114
|
+
- lib/graphql/filters/error.rb
|
|
115
115
|
- lib/graphql/filters/filterable.rb
|
|
116
116
|
- lib/graphql/filters/input_types/base_comparison_input_type.rb
|
|
117
117
|
- lib/graphql/filters/input_types/base_list_comparison_input_type.rb
|
|
@@ -131,7 +131,6 @@ metadata:
|
|
|
131
131
|
homepage_uri: https://github.com/moku-io/graphql-filters
|
|
132
132
|
source_code_uri: https://github.com/moku-io/graphql-filters
|
|
133
133
|
changelog_uri: https://github.com/moku-io/graphql-filters/blob/master/CHANGELOG.md
|
|
134
|
-
post_install_message:
|
|
135
134
|
rdoc_options: []
|
|
136
135
|
require_paths:
|
|
137
136
|
- lib
|
|
@@ -146,8 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
146
145
|
- !ruby/object:Gem::Version
|
|
147
146
|
version: '0'
|
|
148
147
|
requirements: []
|
|
149
|
-
rubygems_version: 3.
|
|
150
|
-
signing_key:
|
|
148
|
+
rubygems_version: 3.6.7
|
|
151
149
|
specification_version: 4
|
|
152
150
|
summary: Provide a fully typed interface to filter lists in a GraphQL API.
|
|
153
151
|
test_files: []
|