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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4c44c55c15ade9472e7683e8929ac05e69df73da70a670888ba6882799c336e
4
- data.tar.gz: ca4865a5a2b629d531ef7e3ef93f899330be5cd0b3854f998fd8ff01d985afd9
3
+ metadata.gz: af871adf4f20074267e23317255e81bff397756f724200e340d7d1dc878adb23
4
+ data.tar.gz: eaa5d6246257da87995933a8fd56ef0666a6b4e29babc24e34079fcc786f45c0
5
5
  SHA512:
6
- metadata.gz: 4975b8a331db108674f279c08863382164ccc2df2e004bf435e8d6ee428a499ae7c2e963dca6c34876dd8f175843590ba1b7ca7ec6351c7d5ae5f1e39df9d2c2
7
- data.tar.gz: 8ffcbcd8615883bc5a9f35757443006bc08debc157015f414724f4aff45b978de35adc8a8687575c12eb677f4183c1ce8b16e44d54fa8c2d5a3b8275ec234a2b
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.
@@ -0,0 +1,5 @@
1
+ module GraphQL
2
+ module Filters
3
+ class Error < StandardError; end
4
+ end
5
+ end
@@ -55,7 +55,18 @@ module GraphQL
55
55
 
56
56
  scope.and nested_query
57
57
  else
58
- scope.where association_name => nested_query
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{v(?<version>\d)+/(?<full_pattern>.*)} do |match_data|
23
- raise 'The only supported version of pattern is v1' if match_data[:version].to_i != 1
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
- resolve_v1 column_node, match_data[:full_pattern]
26
- end
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>.*)} do |match_data|
31
- options = match_data[:options].chars.map(&:to_sym)
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
- if options.present? && options != [:i]
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
- case_sensitive = !options.include?(:i)
38
+ if options.present? && options != [:i]
39
+ raise InvalidMatchError, 'The only supported option is \'i\' for case insensitive matching'
40
+ end
38
41
 
39
- characters = match_data[:pattern].chars
40
- pattern = ''
42
+ case_sensitive = !options.include?(:i)
41
43
 
42
- while characters.present?
43
- char = characters.shift
44
+ characters = match_data[:pattern].chars
45
+ pattern = ''
44
46
 
45
- pattern << case char
46
- when '\\'
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
- column_node.matches pattern, nil, case_sensitive
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
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Filters
3
- VERSION = '1.1.5'.freeze
3
+ VERSION = '1.1.7'.freeze
4
4
  end
5
5
  end
@@ -21,6 +21,7 @@ end
21
21
 
22
22
  # These need to be here, after the definition of GraphQL::Filters
23
23
 
24
+ require_relative 'filters/error'
24
25
  require_relative 'filters/activerecord_patch'
25
26
  require_relative 'filters/dsl'
26
27
  require_relative 'filters/filterable'
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.5
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: 2025-08-18 00:00:00.000000000 Z
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.5.22
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: []