graphiti_graphql 0.1.1 → 0.1.2

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.
@@ -4,22 +4,28 @@ module GraphitiGraphQL
4
4
  query = GraphQL::Query.new(schema, query_string, variables: variables)
5
5
  definition = query.document.definitions.first
6
6
  selection = definition.selections.first
7
- resource_class = find_entrypoint_resource_class(selection.name)
7
+ is_graphiti = schemas.generated.query_field?(selection.name)
8
8
 
9
- # TODO: instead, keep track of fields we add
9
+ # Wrap *everything* in context, in case of federated request
10
10
  Util.with_gql_context do
11
- if resource_class
11
+ if is_graphiti
12
+ resource_class = schemas.generated
13
+ .resource_for_query_field(selection.name)
12
14
  run_query(schema, resource_class, selection, query)
13
15
  else
14
- Graphiti.graphql_schema.schema.execute query_string,
16
+ schemas.graphql.execute query_string,
15
17
  variables: variables,
16
- context: GraphitiGraphQL.get_context
18
+ context: GraphitiGraphQL.config.get_context
17
19
  end
18
20
  end
19
21
  end
20
22
 
21
23
  private
22
24
 
25
+ def schemas
26
+ GraphitiGraphQL.schemas
27
+ end
28
+
23
29
  def run_query(schema, resource_class, selection, query)
24
30
  if (errors = collect_errors(schema, query)).any?
25
31
  {"errors" => errors.map(&:to_h)}
@@ -49,20 +55,8 @@ module GraphitiGraphQL
49
55
  query.validation_errors + query.analysis_errors + query.context.errors
50
56
  end
51
57
 
52
- # We can't just constantize the name from the schema
53
- # Because classes can be reopened and modified in tests (or elsewhere, in theory)
54
- def find_entrypoint_resource_class(entrypoint)
55
- Graphiti.resources.find(&matches_entrypoint?(entrypoint))
56
- end
57
-
58
58
  def find_entrypoint_schema_resource(entrypoint)
59
- graphiti_schema.resources.find(&matches_entrypoint?(entrypoint))
60
- end
61
-
62
- def matches_entrypoint?(entrypoint)
63
- lambda do |resource|
64
- resource.graphql_entrypoint.to_s.underscore == entrypoint.pluralize.underscore
65
- end
59
+ schemas.generated.schema_resource_for_query_field(entrypoint)
66
60
  end
67
61
 
68
62
  def introspection_query?(query)
@@ -70,14 +64,10 @@ module GraphitiGraphQL
70
64
  end
71
65
 
72
66
  def find_resource_by_selection_name(name)
73
- graphiti_schema.resources
67
+ schemas.graphiti.resources
74
68
  .find { |r| r.type == name.pluralize.underscore }
75
69
  end
76
70
 
77
- def graphiti_schema
78
- Graphiti.graphql_schema.graphiti_schema
79
- end
80
-
81
71
  def schema_resource_for_selection(selection, parent_resource)
82
72
  if parent_resource
83
73
  parent_resource.related_resource(selection.name.underscore.to_sym)
@@ -146,8 +136,8 @@ module GraphitiGraphQL
146
136
  end
147
137
 
148
138
  fragments.each do |fragment|
149
- resource_name = Graphiti.graphql_schema.type_registry[fragment.type.name][:resource]
150
- klass = graphiti_schema.resources.find { |r| r.name == resource_name }
139
+ resource_name = schemas.generated.type_registry[fragment.type.name][:resource]
140
+ klass = schemas.graphiti.resources.find { |r| r.name == resource_name }
151
141
  _, _, fragment_sideload_selections = gather_fields fragment.selections,
152
142
  klass,
153
143
  params,
@@ -34,6 +34,7 @@ module GraphitiGraphQL
34
34
  end
35
35
 
36
36
  attr_accessor :type_registry, :schema, :graphiti_schema
37
+ attr_reader :query_fields
37
38
 
38
39
  def self.federation?
39
40
  !!@federation
@@ -54,26 +55,11 @@ module GraphitiGraphQL
54
55
  def self.generate(entrypoint_resources = nil)
55
56
  instance = new
56
57
  schema = Class.new(::GraphitiGraphQL.schema_class || GraphQL::Schema)
57
-
58
- if federation?
59
- schema.send(:include, ApolloFederation::Schema)
60
- end
61
-
62
58
  graphiti_schema = GraphitiGraphQL::GraphitiSchema::Wrapper
63
59
  .new(Graphiti::Schema.generate)
64
- # TODO: if we avoid this on federation, or remove altogether
65
- # Make sure we don't blow up
66
- # graphiti_schema.merge_remotes!
67
-
68
- entries = entrypoint_resources || entrypoints
69
- instance.apply_query(graphiti_schema, schema, entries)
70
-
71
- # NB if we add mutation support, make sure this is applied after
72
- if federation?
73
- schema.use GraphQL::Batch
74
- end
75
- instance.schema = schema
76
60
  instance.graphiti_schema = graphiti_schema
61
+ instance.schema = schema
62
+ instance.apply_query(entrypoint_resources || entrypoints)
77
63
  instance
78
64
  end
79
65
 
@@ -83,147 +69,32 @@ module GraphitiGraphQL
83
69
 
84
70
  def initialize
85
71
  @type_registry = {}
72
+ @query_fields = {}
86
73
  end
87
74
 
88
- # TODO put this in a Federation::Schema module
89
- # Maybe even the External classes themselves?
90
- # TODO assign/assign_each
91
- def apply_federation(graphiti_schema, graphql_schema)
92
- type_registry.each_pair do |name, config|
93
- if config[:resource]
94
- local_type = config[:type]
95
- local_resource = Graphiti.resources
96
- .find { |r| r.name == config[:resource] }
97
- # TODO: maybe turn off the graphiti debug for these?
98
- local_type.define_singleton_method :resolve_reference do |reference, context, lookahead|
99
- Federation::BelongsToLoader
100
- .for(local_resource, lookahead.selections.map(&:name))
101
- .load(reference[:id])
102
- end
103
- end
104
- end
105
-
106
- # NB: test already registered bc 2 things have same relationship
107
- GraphitiGraphQL::Federation.external_resources.each_pair do |klass_name, config|
108
- pre_registered = !!type_registry[klass_name]
109
- external_klass = if pre_registered
110
- type_registry[klass_name][:type]
111
- else
112
- external_klass = Class.new(self.class.base_object)
113
- external_klass.graphql_name klass_name
114
- external_klass
115
- end
116
-
117
- unless pre_registered
118
- external_klass.key(fields: "id")
119
- external_klass.extend_type
120
- external_klass.field :id, String, null: false, external: true
121
- external_klass.class_eval do
122
- def self.resolve_reference(reference, _context, _lookup)
123
- reference
124
- end
125
- end
126
- end
127
-
128
- unless pre_registered
129
- # NB must be registered before processing rels
130
- type_registry[klass_name] = { type: external_klass }
131
- end
132
-
133
- # TODO: only do it if field not already defined
134
- config.relationships.each_pair do |name, relationship|
135
- if relationship.has_many?
136
- define_federated_has_many(graphiti_schema, external_klass, relationship)
137
- elsif relationship.belongs_to?
138
- define_federated_belongs_to(config, relationship)
139
- end
140
- end
141
- end
142
- end
143
-
144
- # TODO: refactor to not constantly pass schemas around
145
- def define_federated_has_many(graphiti_schema, external_klass, relationship)
146
- local_name = GraphitiGraphQL::GraphitiSchema::Resource
147
- .gql_name(relationship.local_resource_class.name)
148
- local_type = type_registry[local_name][:type]
149
- local_resource_name = type_registry[local_name][:resource]
150
- local_resource = Graphiti.resources.find { |r| r.name == local_resource_name }
151
-
152
- local_interface = type_registry["I#{local_name}"]
153
- best_type = local_interface ? local_interface[:type] : local_type
154
-
155
- field = external_klass.field relationship.name,
156
- [best_type],
157
- null: false,
158
- extras: [:lookahead]
159
-
160
- define_arguments_for_sideload_field(field, graphiti_schema.get_resource(local_resource_name))
161
- external_klass.define_method relationship.name do |lookahead:, **arguments|
162
- # TODO test params...do version of sort with array/symbol keys and plain string
163
- params = arguments.as_json
164
- .deep_transform_keys { |key| key.to_s.underscore.to_sym }
165
- selections = lookahead.selections.map(&:name)
166
- selections << relationship.foreign_key
167
- selections << :_type # polymorphism
168
- params[:fields] = { local_resource.type => selections.join(",") }
169
-
170
- if (sort = Util.parse_sort(params[:sort]))
171
- params[:sort] = sort
172
- end
173
-
174
- Federation::HasManyLoader
175
- .for(local_resource, params, relationship.foreign_key)
176
- .load(object[:id])
177
- end
178
- end
179
-
180
- def define_federated_belongs_to(external_resource_config, relationship)
181
- type_name = GraphitiSchema::Resource.gql_name(relationship.local_resource_class.name)
182
- local_type = type_registry[type_name][:type]
183
-
184
- # Todo maybe better way here
185
- interface = type_registry["I#{type_name}"]
186
-
187
- local_type = interface[:type] if interface
188
- local_resource_name = type_registry[type_name][:resource]
189
- local_resource_class = Graphiti.resources.find { |r| r.name == local_resource_name }
190
-
191
- local_types = [local_type]
192
- if interface
193
- local_types |= interface[:implementers]
194
- end
195
-
196
- local_types.each do |local|
197
- local.field relationship.name,
198
- type_registry[external_resource_config.type_name][:type], # todo need to define the type?
199
- null: true
200
- end
201
- end
202
-
203
- def apply_query(graphiti_schema, graphql_schema, entries)
204
- query_type = generate_schema_query(graphql_schema, graphiti_schema, entries)
205
- if self.class.federation?
206
- apply_federation(graphiti_schema, schema)
207
- end
75
+ def apply_query(entries)
76
+ query_type = generate_schema_query(entries)
77
+ Federation::SchemaDecorator.decorate(self) if self.class.federation?
208
78
 
209
79
  # NB - don't call .query here of federation will break things
210
- if graphql_schema.instance_variable_get(:@query_object)
211
- graphql_schema.instance_variable_set(:@query_object, nil)
212
- graphql_schema.instance_variable_set(:@federation_query_object, nil)
80
+ if schema.instance_variable_get(:@query_object)
81
+ schema.instance_variable_set(:@query_object, nil)
82
+ schema.instance_variable_set(:@federation_query_object, nil)
213
83
  end
214
- graphql_schema.orphan_types(orphans(graphql_schema))
215
- graphql_schema.query(query_type)
216
- graphql_schema.query # Actually fires the federation code
84
+ schema.orphan_types(orphans(schema))
85
+ schema.query(query_type)
86
+ schema.query # Actually fires the federation code
217
87
  end
218
88
 
219
- def generate_schema_query(graphql_schema, graphiti_schema, entrypoint_resources = nil)
220
- existing_query = graphql_schema.instance_variable_get(:@query) || graphql_schema.send(:find_inherited_value, :query)
89
+ def generate_schema_query(entrypoint_resources = nil)
90
+ existing_query = schema.instance_variable_get(:@query)
91
+ existing_query ||= schema.send(:find_inherited_value, :query)
221
92
  # NB - don't call graphql_schema.query here of federation will break things
222
93
  query_class = Class.new(existing_query || self.class.base_object)
223
94
  # NB MUST be Query or federation-ruby will break things
224
95
  query_class.graphql_name "Query"
225
96
 
226
- entrypoints(graphiti_schema, entrypoint_resources).each do |resource|
97
+ get_entrypoints(entrypoint_resources).each do |resource|
227
98
  next if resource.remote?
228
99
  generate_type(resource)
229
100
 
@@ -244,28 +115,46 @@ module GraphitiGraphQL
244
115
  end
245
116
  end
246
117
 
118
+ def schema_resource_for_query_field(name)
119
+ @query_fields[name.underscore.to_sym]
120
+ end
121
+
122
+ def query_field?(name)
123
+ @query_fields.include?(name.underscore.to_sym)
124
+ end
125
+
126
+ # We can't just constantize the name from the schema
127
+ # Because classes can be reopened and modified in tests (or elsewhere, in theory)
128
+ def resource_for_query_field(name)
129
+ schema_resource = @query_fields[name.underscore.to_sym]
130
+ Graphiti.resources.find { |r| r.name == schema_resource.name }
131
+ end
132
+
247
133
  private
248
134
 
249
135
  def add_index(query_class, resource)
250
- field = query_class.field resource.graphql_entrypoint,
136
+ field_name = resource.graphql_entrypoint.to_s.underscore.to_sym
137
+ field = query_class.field field_name,
251
138
  [type_registry[resource.graphql_class_name][:type]],
252
139
  "List #{resource.graphql_class_name(false).pluralize}",
253
140
  null: false
141
+ @query_fields[field_name] = resource
254
142
  define_arguments_for_sideload_field(field, resource)
255
143
  end
256
144
 
257
145
  def add_show(query_class, resource)
258
- entrypoint = resource.graphql_entrypoint.to_s.singularize.to_sym
259
- field = query_class.field entrypoint,
146
+ field_name = resource.graphql_entrypoint.to_s.underscore.singularize.to_sym
147
+ field = query_class.field field_name,
260
148
  type_registry[resource.graphql_class_name][:type],
261
149
  "Single #{resource.graphql_class_name(false).singularize}",
262
150
  null: true
151
+ @query_fields[field_name] = resource
263
152
  define_arguments_for_sideload_field field,
264
153
  resource,
265
154
  top_level_single: true
266
155
  end
267
156
 
268
- def entrypoints(graphiti_schema, manually_specified)
157
+ def get_entrypoints(manually_specified)
269
158
  resources = graphiti_schema.resources
270
159
  if manually_specified
271
160
  resources = resources.select { |r|
@@ -309,13 +198,17 @@ module GraphitiGraphQL
309
198
  if top_level_single
310
199
  field.argument(:id, String, required: true)
311
200
  else
312
- sort_type = generate_sort_type(resource)
313
- field.argument :sort, [sort_type], required: false
201
+ unless resource.sorts.empty?
202
+ sort_type = generate_sort_type(resource)
203
+ field.argument :sort, [sort_type], required: false
204
+ end
314
205
  field.argument :page, PageType, required: false
315
206
 
316
- filter_type = generate_filter_type(field, resource)
317
- required = resource.filters.any? { |name, config| !!config[:required] }
318
- field.argument :filter, filter_type, required: required
207
+ unless resource.filters.empty?
208
+ filter_type = generate_filter_type(field, resource)
209
+ required = resource.filters.any? { |name, config| !!config[:required] }
210
+ field.argument :filter, filter_type, required: required
211
+ end
319
212
  end
320
213
  end
321
214
 
@@ -413,8 +306,9 @@ module GraphitiGraphQL
413
306
  klass = Module.new
414
307
  klass.send(:include, self.class.base_interface)
415
308
  klass.definition_methods do
309
+ # rubocop:disable Lint/NestedMethodDefinition(Standard)
416
310
  def resolve_type(object, context)
417
- Graphiti.graphql_schema.schema.types[object[:__typename]]
311
+ GraphitiGraphQL.schemas.graphql.types[object[:__typename]]
418
312
  end
419
313
  end
420
314
  else
@@ -426,10 +320,6 @@ module GraphitiGraphQL
426
320
  implement(klass, type_registry[implements])
427
321
  end
428
322
 
429
- if self.class.federation?
430
- klass.key fields: "id"
431
- end
432
-
433
323
  klass.field(:_type, String, null: false)
434
324
  resource.all_attributes.each do |name, config|
435
325
  if config[:readable]
@@ -24,4 +24,4 @@ module GraphitiGraphQL
24
24
  end
25
25
  end
26
26
  end
27
- end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module GraphitiGraphQL
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti_graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-01 00:00:00.000000000 Z
11
+ date: 2021-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: graphiti
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.2.33
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.2.33
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: activesupport
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -227,6 +241,13 @@ files:
227
241
  - lib/graphiti_graphql/engine.rb
228
242
  - lib/graphiti_graphql/errors.rb
229
243
  - lib/graphiti_graphql/federation.rb
244
+ - lib/graphiti_graphql/federation/apollo_federation_override.rb
245
+ - lib/graphiti_graphql/federation/federated_relationship.rb
246
+ - lib/graphiti_graphql/federation/federated_resource.rb
247
+ - lib/graphiti_graphql/federation/loaders/belongs_to.rb
248
+ - lib/graphiti_graphql/federation/loaders/has_many.rb
249
+ - lib/graphiti_graphql/federation/resource_dsl.rb
250
+ - lib/graphiti_graphql/federation/schema_decorator.rb
230
251
  - lib/graphiti_graphql/graphiti_schema/resource.rb
231
252
  - lib/graphiti_graphql/graphiti_schema/sideload.rb
232
253
  - lib/graphiti_graphql/graphiti_schema/wrapper.rb