lighthouse-graphql 0.5.0 → 0.7.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +212 -1
  3. data/README.md +38 -22
  4. data/docs/best-practices.md +6 -4
  5. data/docs/caching.md +4 -3
  6. data/docs/configuration.md +20 -26
  7. data/docs/custom-directives.md +2 -1
  8. data/docs/directives.md +79 -14
  9. data/docs/federation.md +1 -1
  10. data/docs/filtering-and-ordering.md +330 -30
  11. data/docs/generators.md +42 -40
  12. data/docs/getting-started.md +128 -58
  13. data/docs/motivation.md +3 -2
  14. data/docs/performance.md +5 -4
  15. data/docs/relationships.md +7 -5
  16. data/docs/relay.md +63 -0
  17. data/docs/resolvers.md +92 -39
  18. data/docs/schema-organisation.md +177 -0
  19. data/docs/security.md +64 -16
  20. data/docs/subscriptions.md +16 -9
  21. data/docs/types.md +16 -17
  22. data/docs/uploads.md +41 -14
  23. data/lib/generators/lighthouse/base_classes.rb +9 -14
  24. data/lib/generators/lighthouse/install/graphql_ruby_install.rb +113 -0
  25. data/lib/generators/lighthouse/install/install_generator.rb +125 -99
  26. data/lib/generators/lighthouse/install/templates/graphql.config.yml.tt +2 -1
  27. data/lib/generators/lighthouse/install/templates/initializer.rb.tt +14 -13
  28. data/lib/lighthouse/ast_builder.rb +124 -0
  29. data/lib/lighthouse/caching.rb +9 -3
  30. data/lib/lighthouse/config.rb +27 -25
  31. data/lib/lighthouse/contracts/arg_manipulator.rb +9 -5
  32. data/lib/lighthouse/contracts/field_builder.rb +13 -0
  33. data/lib/lighthouse/controller.rb +28 -5
  34. data/lib/lighthouse/directive_resolvers/aggregate_resolver.rb +1 -2
  35. data/lib/lighthouse/directive_resolvers/all_resolver.rb +0 -2
  36. data/lib/lighthouse/directive_resolvers/argument_transforms_resolver.rb +18 -18
  37. data/lib/lighthouse/directive_resolvers/association_resolvers.rb +9 -6
  38. data/lib/lighthouse/directive_resolvers/base.rb +6 -37
  39. data/lib/lighthouse/directive_resolvers/builder_applier.rb +21 -6
  40. data/lib/lighthouse/directive_resolvers/cache_resolvers.rb +1 -1
  41. data/lib/lighthouse/directive_resolvers/count_resolver.rb +0 -1
  42. data/lib/lighthouse/directive_resolvers/first_resolver.rb +0 -2
  43. data/lib/lighthouse/directive_resolvers/global_id_resolver.rb +3 -1
  44. data/lib/lighthouse/directive_resolvers/paginate_resolver.rb +2 -3
  45. data/lib/lighthouse/directive_resolvers/throttle_resolver.rb +85 -26
  46. data/lib/lighthouse/directive_resolvers/type_method_resolver.rb +12 -6
  47. data/lib/lighthouse/directives/arguments/more_filters.rb +1 -1
  48. data/lib/lighthouse/directives/arguments/order_by.rb +190 -26
  49. data/lib/lighthouse/directives/arguments/where_conditions.rb +25 -0
  50. data/lib/lighthouse/directives/arguments/where_conditions_base.rb +84 -0
  51. data/lib/lighthouse/directives/arguments/where_has_conditions.rb +56 -0
  52. data/lib/lighthouse/directives/definitions.rb +0 -1
  53. data/lib/lighthouse/directives/generated_types.rb +103 -0
  54. data/lib/lighthouse/directives/registry.rb +39 -10
  55. data/lib/lighthouse/document_ast.rb +111 -0
  56. data/lib/lighthouse/field_resolver_stack.rb +9 -6
  57. data/lib/lighthouse/global_id.rb +29 -3
  58. data/lib/lighthouse/graphql/version.rb +1 -1
  59. data/lib/lighthouse/graphql_ruby_patches.rb +45 -0
  60. data/lib/lighthouse/ide_helper.rb +80 -23
  61. data/lib/lighthouse/namespaces.rb +1 -0
  62. data/lib/lighthouse/order_by/types.rb +137 -0
  63. data/lib/lighthouse/railtie.rb +9 -2
  64. data/lib/lighthouse/rb_lighthouse.rb +16 -5
  65. data/lib/lighthouse/resolve_info.rb +5 -5
  66. data/lib/lighthouse/resolver_bases.rb +18 -4
  67. data/lib/lighthouse/resolver_dispatch.rb +16 -1
  68. data/lib/lighthouse/root_types.rb +117 -0
  69. data/lib/lighthouse/schema_cache.rb +8 -4
  70. data/lib/lighthouse/schema_factory.rb +6 -9
  71. data/lib/lighthouse/schema_generator.rb +8 -18
  72. data/lib/lighthouse/schema_stitcher.rb +64 -0
  73. data/lib/lighthouse/support/naming.rb +0 -12
  74. data/lib/lighthouse/upload.rb +4 -2
  75. data/lib/lighthouse/upload_store.rb +39 -14
  76. data/lib/lighthouse/where_conditions/columns.rb +36 -0
  77. data/lib/lighthouse/where_conditions/handler.rb +152 -0
  78. data/lib/lighthouse/where_conditions/json_column.rb +99 -0
  79. data/lib/lighthouse/where_conditions/operator_map.rb +1 -4
  80. data/lib/lighthouse/where_conditions/types.rb +110 -0
  81. data/sig/lighthouse/directives.rbs +16 -3
  82. data/sig/lighthouse/graphql.rbs +48 -4
  83. data/sig/lighthouse.rbs +8 -12
  84. metadata +17 -7
  85. data/lib/generators/lighthouse/install/templates/schema.rb.tt +0 -29
  86. data/lib/generators/lighthouse/mutation_generator.rb +0 -31
  87. data/lib/generators/lighthouse/query_generator.rb +0 -30
  88. data/lib/lighthouse/directive_resolvers/where_conditions_applier.rb +0 -170
  89. data/lib/lighthouse/directives/where_conditions_directive.rb +0 -244
  90. data/lib/lighthouse/sdl_loader.rb +0 -22
@@ -36,9 +36,17 @@ module Lighthouse
36
36
  include Directive
37
37
  end
38
38
 
39
+ # Its instances, built with (arg_node, directive_node), answer
40
+ # `manipulate_arg_definition(document_ast, field_node, type_node)` with the argument node.
39
41
  module ArgManipulator
40
42
  include Directive
41
43
  end
44
+
45
+ # Its instances, built with (field_node, directive_node), answer
46
+ # `handle_field_builder(relation)` with the constrained relation.
47
+ module FieldBuilder
48
+ include Directive
49
+ end
42
50
  end
43
51
 
44
52
  class DirectiveRegistry
@@ -67,6 +75,9 @@ module Lighthouse
67
75
  def resolution_names: () -> Array[String]
68
76
  def arg_builder_class: (String | Symbol name) -> Class?
69
77
  def arg_builder_names: () -> Array[String]
78
+ def arg_manipulator_class: (String | Symbol name) -> Class?
79
+ def arg_manipulator_classes: () -> Array[Class]
80
+ def field_builder_class: (String | Symbol name) -> Class?
70
81
 
71
82
  # { "name" => "directive @name(...) on ..." }
72
83
  def directive_definitions: () -> Hash[String, String]
@@ -83,9 +94,11 @@ module Lighthouse
83
94
  PROGRAMMATIC_TYPES_FILE: String
84
95
 
85
96
  # Writes both files into `output_dir` and returns their paths.
86
- def self?.write: (output_dir: path, ?sdl_folder: path?, ?registry: DirectiveRegistry) -> Array[String]
97
+ def self?.write: (output_dir: path, ?schema_path: path?, ?registry: DirectiveRegistry) -> Array[String]
98
+ # Config.ide_helper's rewrite, on boot and every reload.
99
+ def self?.refresh: (?output_dir: path) -> Array[String]
87
100
  def self?.directives_sdl: (?String sdl, ?registry: DirectiveRegistry) -> String
88
- def self?.programmatic_types_sdl: (String sdl, ?registry: DirectiveRegistry) -> String
101
+ def self?.programmatic_types_sdl: (String sdl, ?registry: DirectiveRegistry, ?directives: String) -> String
89
102
  end
90
103
 
91
104
  module GraphQL
@@ -107,7 +120,7 @@ module Lighthouse
107
120
  # `ctx`: the query's context, which @builder on an argument receives.
108
121
  def apply_where_filters: (relation relation, untyped args, ?context? ctx) -> relation
109
122
  def apply_arg_builders: (relation relation, arguments args_h, ?context? ctx) -> relation
110
- def extract_relation_name_from_directive: (untyped arg_node) -> String?
123
+ def apply_field_builders: (relation relation, ?arguments args_h, ?context? ctx) -> relation
111
124
  def constantize_model: (String? name) -> Class?
112
125
  def apply_scopes: (relation relation) -> relation
113
126
 
@@ -9,16 +9,60 @@ module Lighthouse
9
9
  type schema = untyped
10
10
 
11
11
  module RbLightHouse
12
- # The schema built from the SDL in `sdl_folder` (default: Config.sdl_folder).
12
+ # The schema built from the SDL `schema_path` (default: Config.schema_path) and its imports.
13
13
  # `options` takes :base_types, :base_schema and the schema's own options.
14
- def self.get_schema: (?sdl_folder: path?, ?options: Hash[Symbol, untyped]) -> schema
14
+ def self.get_schema: (?schema_path: path?, ?options: Hash[Symbol, untyped]) -> schema
15
15
  def self.configure: () ?{ (singleton(Lighthouse::Config) config) -> void } -> void
16
16
  end
17
17
 
18
18
  module SchemaFactory
19
- def self?.build_base_schema: (?sdl_folder: path?, ?sdl: String?, ?base_types: Hash[untyped, untyped],
19
+ def self?.build_base_schema: (?schema_path: path?, ?sdl: String?, ?base_types: Hash[untyped, untyped],
20
20
  ?schema_options: Hash[Symbol, untyped], **untyped extra_options) -> schema
21
- def self?.load_sdl: (path? sdl_folder) -> String
21
+ def self?.load_sdl: (?path? schema_path) -> String
22
+ end
23
+
24
+ # Reads the schema from its entrypoint, pasting in what it #imports.
25
+ class SchemaStitcher
26
+ IMPORT: String
27
+
28
+ attr_reader root_schema_path: String
29
+
30
+ def initialize: (path root_schema_path) -> void
31
+ def schema_string: () -> String
32
+ # Every file the schema is read from, the entrypoint first.
33
+ def files: () -> Array[String]
34
+ end
35
+
36
+ # The schema document the manipulation phase works on: each type defined once.
37
+ class DocumentAST
38
+ KEYWORDS: Hash[Class, String]
39
+ EXTENSIONS: Array[Class]
40
+
41
+ attr_reader types: Hash[String, untyped]
42
+ attr_reader type_extensions: Hash[String, Array[untyped]]
43
+ attr_reader directives: Hash[String, untyped]
44
+ attr_reader schema_definitions: Array[untyped]
45
+
46
+ def self.from_source: (String sdl) -> DocumentAST
47
+ def self.from_document: (untyped document) -> DocumentAST
48
+
49
+ def initialize: () -> void
50
+ def add: (untyped definition) -> untyped
51
+ # Adds a type, or replaces the one of the same name.
52
+ def put_type: (untyped definition) -> self
53
+ # Adds the type parsed from the SDL (or the block's), unless one of the name exists.
54
+ def define_type: (String name, ?String? sdl) ?{ () -> String } -> untyped
55
+ def type?: (String name) -> bool
56
+ def type_names: () -> Array[String]
57
+ def to_document: () -> untyped
58
+ def to_sdl: () -> String
59
+ end
60
+
61
+ # The manipulation phase: extensions merged, then the type, field and argument manipulators.
62
+ module ASTBuilder
63
+ ROOT_TYPES: Array[String]
64
+
65
+ def self?.build: (String sdl, ?registry: DirectiveRegistry) -> String
22
66
  end
23
67
 
24
68
  # Apollo's Automatic Persisted Queries, and a manifest of trusted operations.
data/sig/lighthouse.rbs CHANGED
@@ -45,12 +45,6 @@ module Lighthouse
45
45
  def decode: (String global_id) -> Array[String]
46
46
  end
47
47
 
48
- # Config.upload_store: stores the file an @upload argument carries, and returns
49
- # what the argument holds instead (an ActiveStorage blob's signed id by default).
50
- interface _UploadStore
51
- def call: (untyped file, ?disk: String?, ?path: String?, ?public: bool?) -> untyped
52
- end
53
-
54
48
  # Config.authenticated_user and Config.policy_user: the user of a request.
55
49
  interface _UserReader
56
50
  def call: (context? context) -> untyped
@@ -98,8 +92,14 @@ module Lighthouse
98
92
  def self.[]: (Symbol | String key) -> untyped
99
93
  def self.reset!: () -> void
100
94
 
101
- def self.sdl_folder: () -> path?
102
- def self.sdl_folder=: (path? folder) -> path?
95
+ # The schema's entrypoint, which imports the other files (SchemaStitcher).
96
+ def self.schema_path: () -> path?
97
+ def self.schema_path=: (path? path) -> path?
98
+ def self.schema_directory: () -> String?
99
+
100
+ # Rewrite the IDE helper files on boot and every reload; nil: in development with a graphql.config.yml.
101
+ def self.ide_helper: () -> bool?
102
+ def self.ide_helper=: (bool? enabled) -> bool?
103
103
 
104
104
  # { queries:, mutations:, subscriptions:, builders:, validators:, events: } => [namespace, ...].
105
105
  # Assigning a partial hash overrides only the kinds it names.
@@ -112,8 +112,6 @@ module Lighthouse
112
112
  def self.directive_map: () -> Hash[untyped, untyped]
113
113
 
114
114
  # An ActiveSupport::Cache::Store; Rails.cache by default.
115
- def self.cache_store: () -> untyped
116
- def self.cache_store=: (untyped store) -> untyped
117
115
 
118
116
  def self.transactional_mutations: () -> bool
119
117
  def self.transactional_mutations=: (bool? enabled) -> bool?
@@ -147,8 +145,6 @@ module Lighthouse
147
145
  def self.global_id: () -> _GlobalId
148
146
  def self.global_id=: (_GlobalId? codec) -> _GlobalId?
149
147
 
150
- def self.upload_store: () -> _UploadStore
151
- def self.upload_store=: (_UploadStore? store) -> _UploadStore?
152
148
 
153
149
  # Whether @broadcast triggers in an ActiveJob (Lighthouse::GraphQL::BroadcastJob)
154
150
  # unless the directive says shouldQueue; and the job's queue (nil: ActiveJob's default).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lighthouse-graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vaz Innovation
@@ -70,6 +70,7 @@ files:
70
70
  - docs/relationships.md
71
71
  - docs/relay.md
72
72
  - docs/resolvers.md
73
+ - docs/schema-organisation.md
73
74
  - docs/security.md
74
75
  - docs/subscriptions.md
75
76
  - docs/types.md
@@ -78,17 +79,16 @@ files:
78
79
  - lib/generators/lighthouse/base_classes.rb
79
80
  - lib/generators/lighthouse/builder_generator.rb
80
81
  - lib/generators/lighthouse/class_generator.rb
82
+ - lib/generators/lighthouse/install/graphql_ruby_install.rb
81
83
  - lib/generators/lighthouse/install/install_generator.rb
82
84
  - lib/generators/lighthouse/install/templates/graphql.config.yml.tt
83
85
  - lib/generators/lighthouse/install/templates/graphql_controller.rb.tt
84
86
  - lib/generators/lighthouse/install/templates/initializer.rb.tt
85
87
  - lib/generators/lighthouse/install/templates/schema.graphql.tt
86
- - lib/generators/lighthouse/install/templates/schema.rb.tt
87
- - lib/generators/lighthouse/mutation_generator.rb
88
- - lib/generators/lighthouse/query_generator.rb
89
88
  - lib/generators/lighthouse/subscription_generator.rb
90
89
  - lib/generators/lighthouse/validator_generator.rb
91
90
  - lib/lighthouse-graphql.rb
91
+ - lib/lighthouse/ast_builder.rb
92
92
  - lib/lighthouse/async_mutation_job.rb
93
93
  - lib/lighthouse/broadcast_job.rb
94
94
  - lib/lighthouse/broadcasting.rb
@@ -100,6 +100,7 @@ files:
100
100
  - lib/lighthouse/contracts/arg_builder.rb
101
101
  - lib/lighthouse/contracts/arg_manipulator.rb
102
102
  - lib/lighthouse/contracts/directive.rb
103
+ - lib/lighthouse/contracts/field_builder.rb
103
104
  - lib/lighthouse/contracts/field_manipulator.rb
104
105
  - lib/lighthouse/contracts/field_middleware.rb
105
106
  - lib/lighthouse/contracts/field_resolver.rb
@@ -145,7 +146,6 @@ files:
145
146
  - lib/lighthouse/directive_resolvers/throttle_resolver.rb
146
147
  - lib/lighthouse/directive_resolvers/type_method_resolver.rb
147
148
  - lib/lighthouse/directive_resolvers/validator_resolver.rb
148
- - lib/lighthouse/directive_resolvers/where_conditions_applier.rb
149
149
  - lib/lighthouse/directive_resolvers/with_count_resolver.rb
150
150
  - lib/lighthouse/directive_resolvers/with_resolver.rb
151
151
  - lib/lighthouse/directives/arguments/base.rb
@@ -153,7 +153,11 @@ files:
153
153
  - lib/lighthouse/directives/arguments/more_filters.rb
154
154
  - lib/lighthouse/directives/arguments/order_by.rb
155
155
  - lib/lighthouse/directives/arguments/soft_deletes.rb
156
+ - lib/lighthouse/directives/arguments/where_conditions.rb
157
+ - lib/lighthouse/directives/arguments/where_conditions_base.rb
158
+ - lib/lighthouse/directives/arguments/where_has_conditions.rb
156
159
  - lib/lighthouse/directives/definitions.rb
160
+ - lib/lighthouse/directives/generated_types.rb
157
161
  - lib/lighthouse/directives/model_directive.rb
158
162
  - lib/lighthouse/directives/node_directive.rb
159
163
  - lib/lighthouse/directives/pagination_info_types.rb
@@ -161,7 +165,7 @@ files:
161
165
  - lib/lighthouse/directives/registry.rb
162
166
  - lib/lighthouse/directives/soft_deletes_directive.rb
163
167
  - lib/lighthouse/directives/visibility_directive.rb
164
- - lib/lighthouse/directives/where_conditions_directive.rb
168
+ - lib/lighthouse/document_ast.rb
165
169
  - lib/lighthouse/enum_directive.rb
166
170
  - lib/lighthouse/errors.rb
167
171
  - lib/lighthouse/events.rb
@@ -186,6 +190,7 @@ files:
186
190
  - lib/lighthouse/nested_mutations/many_to_many_operations.rb
187
191
  - lib/lighthouse/nested_mutations/partitioner.rb
188
192
  - lib/lighthouse/node_registry.rb
193
+ - lib/lighthouse/order_by/types.rb
189
194
  - lib/lighthouse/pagination.rb
190
195
  - lib/lighthouse/persisted_queries.rb
191
196
  - lib/lighthouse/persisted_queries/batch.rb
@@ -197,12 +202,13 @@ files:
197
202
  - lib/lighthouse/resolve_info.rb
198
203
  - lib/lighthouse/resolver_bases.rb
199
204
  - lib/lighthouse/resolver_dispatch.rb
205
+ - lib/lighthouse/root_types.rb
200
206
  - lib/lighthouse/scalar_coercion.rb
201
207
  - lib/lighthouse/schema_cache.rb
202
208
  - lib/lighthouse/schema_factory.rb
203
209
  - lib/lighthouse/schema_generator.rb
204
210
  - lib/lighthouse/schema_implementation.rb
205
- - lib/lighthouse/sdl_loader.rb
211
+ - lib/lighthouse/schema_stitcher.rb
206
212
  - lib/lighthouse/subscription.rb
207
213
  - lib/lighthouse/support/arguments.rb
208
214
  - lib/lighthouse/support/authorization.rb
@@ -218,7 +224,11 @@ files:
218
224
  - lib/lighthouse/type_resolution.rb
219
225
  - lib/lighthouse/upload.rb
220
226
  - lib/lighthouse/upload_store.rb
227
+ - lib/lighthouse/where_conditions/columns.rb
228
+ - lib/lighthouse/where_conditions/handler.rb
229
+ - lib/lighthouse/where_conditions/json_column.rb
221
230
  - lib/lighthouse/where_conditions/operator_map.rb
231
+ - lib/lighthouse/where_conditions/types.rb
222
232
  - sig/lighthouse.rbs
223
233
  - sig/lighthouse/controller.rbs
224
234
  - sig/lighthouse/directives.rbs
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Executable schema built by Lighthouse from the SDL in <%= sdl_folder %>/. The
4
- # Dataloader batches relationship loads (@hasMany, @belongsTo, ...) to avoid N+1.
5
- # base_types names the base classes: every class a Query, Mutation or Subscription
6
- # field resolves to must inherit from its kind's, and a Subscription field with no
7
- # class of its own resolves with the subscription one. graphql-ruby builds the
8
- # SDL's types from the others (written by `rails g graphql:install`), and type
9
- # classes (Types::PostType, Types::DateTimeType, ...) inherit from them.
10
- <%= schema_name %> = Lighthouse::GraphQL::RbLightHouse.get_schema(
11
- options: {
12
- using: { GraphQL::Dataloader => {} },
13
- base_types: {
14
- resolver: <%= base_class_constant(:queries) %>,
15
- mutation: <%= base_class_constant(:mutations) %>,
16
- subscription: <%= base_class_constant(:subscriptions) %>,
17
- object: Types::BaseObject,
18
- interface: Types::BaseInterface,
19
- union: Types::BaseUnion,
20
- scalar: Types::BaseScalar,
21
- enum: Types::BaseEnum,
22
- input_object: Types::BaseInputObject
23
- }
24
- }
25
- )
26
-
27
- # Refuse expensive queries before they run (see the gem's docs/security.md):
28
- # <%= schema_name %>.max_complexity(300)
29
- # <%= schema_name %>.max_depth(12)
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'class_generator'
4
-
5
- # `rails generate lighthouse:mutation PublishPost` — the class (a
6
- # Mutations::BaseMutation, so a GraphQL::Schema::Mutation) that `Mutation.publishPost`
7
- # resolves to by convention.
8
- class Lighthouse::Generators::MutationGenerator < Lighthouse::Generators::ClassGenerator
9
- desc 'Generate the class of a Mutation field (a Mutations::BaseMutation) in app/graphql/mutations.'
10
-
11
- private
12
-
13
- def kind = :mutations
14
-
15
- def body
16
- <<~RUBY
17
- # Resolves Mutation.#{class_basename.camelize(:lower)}. Keyword arguments are the field's
18
- # arguments in snake_case; `object` and `context` are available. Raise
19
- # GraphQL::ExecutionError for failures the client should see.
20
- class #{class_basename} < #{superclass_name}
21
- def resolve(**args)
22
- raise NotImplementedError
23
- end
24
- end
25
- RUBY
26
- end
27
-
28
- def usage
29
- "Mutation.#{class_basename.camelize(:lower)} resolves to #{full_class_name} — add the field to your SDL without a resolving directive."
30
- end
31
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'class_generator'
4
-
5
- # `rails generate lighthouse:query LatestPosts` — the resolver (a
6
- # Resolvers::BaseResolver, so a GraphQL::Schema::Resolver) that `Query.latestPosts`
7
- # resolves to by convention.
8
- class Lighthouse::Generators::QueryGenerator < Lighthouse::Generators::ClassGenerator
9
- desc 'Generate the resolver of a Query field (a Resolvers::BaseResolver) in app/graphql/resolvers.'
10
-
11
- private
12
-
13
- def kind = :queries
14
-
15
- def body
16
- <<~RUBY
17
- # Resolves Query.#{class_basename.camelize(:lower)}. Keyword arguments are the field's
18
- # arguments in snake_case; `object` and `context` are available.
19
- class #{class_basename} < #{superclass_name}
20
- def resolve(**args)
21
- raise NotImplementedError
22
- end
23
- end
24
- RUBY
25
- end
26
-
27
- def usage
28
- "Query.#{class_basename.camelize(:lower)} resolves to #{full_class_name} — add the field to your SDL without a resolving directive."
29
- end
30
- end
@@ -1,170 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Lighthouse::GraphQL::DirectiveResolvers::WhereConditionsApplier
4
- # Operator semantics live in Lighthouse::WhereConditions::OperatorMap so
5
- # they can be shared with the simple argument-filter directives.
6
- SUPPORTED_OPERATORS = Lighthouse::WhereConditions::OperatorMap::OPERATORS
7
- # graphql-ruby hands input objects over with Ruby-style keys, so the
8
- # generated input's AND/OR/HAS fields arrive as :and/:or/:has.
9
- LOGICAL_KEYS = { and: :AND, or: :OR, has: :HAS }.freeze
10
-
11
- class << self
12
- def apply(relation, value)
13
- return relation unless relation.respond_to?(:where)
14
-
15
- condition = normalize_condition(value)
16
- return relation unless condition
17
-
18
- # AND narrows the incoming scope step by step; each OR branch starts
19
- # from the incoming scope, and their union replaces it.
20
- result = apply_all(relation, condition[:AND])
21
- result = apply_any(relation, condition[:OR]) || result
22
- result = apply_has_condition(result, condition[:HAS])
23
- result = apply_single_condition(result, condition) if condition.key?(:column)
24
- result
25
- end
26
-
27
- def apply_has(relation, has_cond)
28
- relation_name = has_cond[:relation].to_s
29
- return relation if relation_name.blank?
30
-
31
- resolved_relation_name = resolve_relation_name(relation, relation_name)
32
- return relation if resolved_relation_name.blank?
33
-
34
- reflection = reflection_for(relation, resolved_relation_name)
35
- return relation unless reflection&.klass
36
-
37
- joined = relation.joins(resolved_relation_name.to_sym)
38
- return joined.distinct if has_cond[:condition].blank?
39
-
40
- # Use an EXISTS-style filter to safely apply conditions from the related model.
41
- child_scope = apply(reflection.klass.all, has_cond[:condition])
42
- joined.where(reflection.klass.table_name => { id: child_scope.select(:id) }).distinct
43
- rescue StandardError
44
- relation
45
- end
46
-
47
- def resolve_relation_name(relation, relation_name)
48
- candidate_names = [relation_name.to_s, relation_name.to_s.singularize, relation_name.to_s.pluralize].uniq
49
-
50
- candidate_names.each do |candidate|
51
- next if candidate.blank?
52
- return candidate if association_exists?(relation, candidate)
53
- end
54
-
55
- nil
56
- rescue StandardError
57
- nil
58
- end
59
-
60
- def association_exists?(relation, name)
61
- reflection_for(relation, name).present?
62
- rescue StandardError
63
- false
64
- end
65
-
66
- def apply_has_argument(relation, arg_name, arg_value, relation_name = nil)
67
- relation_name ||= derive_relation_name(arg_name)
68
- return relation unless relation_name.present? && arg_value.present?
69
-
70
- condition = arg_value.is_a?(Hash) ? arg_value : symbolized(arg_value)
71
- has_payload = { relation: relation_name, condition: condition }
72
- apply_has(relation, has_payload)
73
- end
74
-
75
- private
76
-
77
- # The condition as a symbol-keyed Hash, or nil when there is none.
78
- def normalize_condition(value)
79
- return nil if value.nil?
80
-
81
- condition = value.is_a?(Hash) ? value.deep_symbolize_keys : symbolized(value)
82
- condition.transform_keys { |key| LOGICAL_KEYS.fetch(key, key) } if condition.is_a?(Hash)
83
- end
84
-
85
- def apply_all(relation, conditions)
86
- Array(conditions).reduce(relation) { |acc, sub| apply(acc, sub) }
87
- end
88
-
89
- # The union of the branches, or nil when there are none.
90
- def apply_any(relation, conditions)
91
- Array(conditions).map { |sub| apply(relation, sub) }.reduce { |acc, sub_scope| acc.or(sub_scope) }
92
- end
93
-
94
- def apply_has_condition(relation, has_cond)
95
- return relation unless has_cond.is_a?(Hash) && has_cond[:relation]
96
-
97
- apply_has(relation, has_cond)
98
- end
99
-
100
- def reflection_for(relation, name)
101
- klass = relation.respond_to?(:klass) ? relation.klass : relation
102
- klass.reflect_on_association(name.to_sym) if klass.respond_to?(:reflect_on_association)
103
- end
104
-
105
- def derive_relation_name(arg_name)
106
- Lighthouse::Support::Naming.derive_relation_name(arg_name)
107
- end
108
-
109
- def apply_single_condition(relation, condition)
110
- return relation if condition[:column].blank? || !relation.respond_to?(:where)
111
-
112
- column_name = resolve_column_name(condition[:column])
113
- return relation if column_name.blank?
114
-
115
- apply_operator(relation, column_name, operator_of(condition), condition[:value])
116
- rescue StandardError
117
- relation
118
- end
119
-
120
- def operator_of(condition)
121
- condition[:operator]&.to_s&.upcase || 'EQ'
122
- end
123
-
124
- # Null and set operators are plain hash conditions; the rest need Arel.
125
- def apply_operator(relation, column_name, operator, value)
126
- case operator
127
- when 'IS_NULL' then relation.where(column_name => nil)
128
- when 'IS_NOT_NULL' then relation.where.not(column_name => nil)
129
- when 'IN' then relation.where(column_name => Array(value))
130
- when 'NIN' then relation.where.not(column_name => Array(value))
131
- else apply_arel_operator(relation, column_name, operator, value)
132
- end
133
- end
134
-
135
- # An unknown operator falls back to equality rather than failing the query.
136
- def apply_arel_operator(relation, column_name, operator, value)
137
- pattern = like_pattern(operator, value)
138
- return relation.where(arel_table(relation)[column_name].matches(pattern)) if pattern
139
-
140
- predicate = SUPPORTED_OPERATORS[operator]
141
- return relation.where(column_name => value) unless predicate
142
-
143
- relation.where(arel_table(relation)[column_name].public_send(predicate, value))
144
- end
145
-
146
- def like_pattern(operator, value)
147
- case operator
148
- when 'CONTAINS' then "%#{value}%"
149
- when 'STARTS_WITH' then "#{value}%"
150
- when 'ENDS_WITH' then "%#{value}"
151
- end
152
- end
153
-
154
- def resolve_column_name(column)
155
- Lighthouse::Support::Naming.column(column)
156
- end
157
-
158
- def arel_table(relation)
159
- relation.klass.arel_table
160
- end
161
-
162
- def symbolized(obj)
163
- if obj.respond_to?(:to_h)
164
- obj.to_h.deep_symbolize_keys
165
- else
166
- {}
167
- end
168
- end
169
- end
170
- end