graphiti_graphql 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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