graphiti_graphql 0.1.0 → 0.1.5

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.
@@ -59,18 +59,14 @@ module GraphitiGraphQL
59
59
  !!config[:remote]
60
60
  end
61
61
 
62
- def fetch_remote_schema!
63
- parts = remote_url.split("/")
64
- parts.pop
65
- url = "#{parts.join("/")}/vandal/schema.json"
66
- response = faraday.get(url)
67
- JSON.parse(response.body).deep_symbolize_keys
68
- end
69
-
70
62
  def name
71
63
  config[:name]
72
64
  end
73
65
 
66
+ def stats
67
+ config[:stats]
68
+ end
69
+
74
70
  def type
75
71
  config[:type]
76
72
  end
@@ -102,16 +98,6 @@ module GraphitiGraphQL
102
98
  def all_attributes
103
99
  attributes.merge(extra_attributes)
104
100
  end
105
-
106
- private
107
-
108
- def faraday
109
- if defined?(Faraday)
110
- Faraday
111
- else
112
- raise "Faraday not defined. Please require the 'faraday' gem to use remote resources"
113
- end
114
- end
115
101
  end
116
102
  end
117
103
  end
@@ -16,21 +16,6 @@ module GraphitiGraphQL
16
16
  def resources
17
17
  schema[:resources].map { |r| get_resource(r[:name]) }
18
18
  end
19
-
20
- # TODO some work here, dupes, refer back, etc
21
- def merge_remotes!
22
- resources.select(&:remote?).each do |resource|
23
- remote_schema = resource.fetch_remote_schema!
24
- remote_schema[:resources].each do |remote_config|
25
- unless resources.map(&:name).include?(remote_config[:name])
26
- remote_config[:name] = resource.name
27
- schema[:resources].reject! { |r| r[:name] == resource.name }
28
- schema[:resources] << remote_config
29
- schema
30
- end
31
- end
32
- end
33
- end
34
19
  end
35
20
  end
36
21
  end
@@ -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)}
@@ -32,7 +38,7 @@ module GraphitiGraphQL
32
38
 
33
39
  def render(json, selection_name)
34
40
  payload = if find_one?(selection_name)
35
- {selection_name.to_sym => json.values[0][0]}
41
+ {selection_name.to_sym => json.values[0][:nodes][0]}
36
42
  else
37
43
  json
38
44
  end
@@ -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)
@@ -118,15 +108,19 @@ module GraphitiGraphQL
118
108
  gather_filters(params, selection, variables_hash, chained_name)
119
109
  gather_sorts(params, selection, variables_hash, chained_name)
120
110
  gather_pages(params, selection, variables_hash, chained_name)
111
+ gather_stats(params, selection, variables_hash, chained_name)
121
112
  end
122
113
 
123
114
  params[:include] ||= []
124
115
  params[:include] << chained_name if chained_name
125
116
 
126
- fragments = selection.selections.select { |s|
117
+ nodes = selection.selections.find { |s| s.respond_to?(:name) && s.name == "nodes" }
118
+ children = nodes ? nodes.children : selection.selections
119
+
120
+ fragments = children.select { |s|
127
121
  s.is_a?(GraphQL::Language::Nodes::InlineFragment)
128
122
  }
129
- non_fragments = selection.selections - fragments
123
+ non_fragments = children - fragments
130
124
 
131
125
  if pbt
132
126
  # Only id/_type possible here
@@ -146,8 +140,8 @@ module GraphitiGraphQL
146
140
  end
147
141
 
148
142
  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 }
143
+ resource_name = schemas.generated.type_registry[fragment.type.name][:resource]
144
+ klass = schemas.graphiti.resources.find { |r| r.name == resource_name }
151
145
  _, _, fragment_sideload_selections = gather_fields fragment.selections,
152
146
  klass,
153
147
  params,
@@ -233,7 +227,9 @@ module GraphitiGraphQL
233
227
 
234
228
  attr_arg.value.arguments.each do |operator_arg|
235
229
  value = operator_arg.value
236
- if value.respond_to?(:name) # is a variable
230
+ if value.is_a?(GraphQL::Language::Nodes::Enum)
231
+ value = value.name
232
+ elsif value.respond_to?(:name) # is a variable
237
233
  value = variable_hash[operator_arg.value.name]
238
234
  end
239
235
  f[filter_param_name] = {operator_arg.name.underscore => value}
@@ -309,5 +305,22 @@ module GraphitiGraphQL
309
305
  params[:page].merge!(pages)
310
306
  end
311
307
  end
308
+
309
+ def gather_stats(params, selection, variable_hash, chained_name = nil)
310
+ stats = selection.children.find { |c| c.name == "stats" }
311
+ nodes = selection.children.find { |c| c.name == "nodes" }
312
+
313
+ if stats
314
+ stat_param = {}
315
+ stats.children.each do |stat_node|
316
+ stat_name = stat_node.name
317
+ calculations = stat_node.children.map(&:name)
318
+ stat_param[stat_name.to_sym] = calculations.join(",")
319
+ end
320
+ stat_param = {chained_name => stat_param} if chained_name
321
+ params[:stats] = stat_param
322
+ params[:page] = {size: 0} unless nodes
323
+ end
324
+ end
312
325
  end
313
326
  end
@@ -3,6 +3,7 @@ module GraphitiGraphQL
3
3
  GQL_TYPE_MAP = {
4
4
  integer_id: String,
5
5
  string: String,
6
+ uuid: String,
6
7
  integer: Integer,
7
8
  float: Float,
8
9
  boolean: GraphQL::Schema::Member::GraphQLTypeNames::Boolean,
@@ -33,6 +34,7 @@ module GraphitiGraphQL
33
34
  end
34
35
 
35
36
  attr_accessor :type_registry, :schema, :graphiti_schema
37
+ attr_reader :query_fields
36
38
 
37
39
  def self.federation?
38
40
  !!@federation
@@ -53,26 +55,11 @@ module GraphitiGraphQL
53
55
  def self.generate(entrypoint_resources = nil)
54
56
  instance = new
55
57
  schema = Class.new(::GraphitiGraphQL.schema_class || GraphQL::Schema)
56
-
57
- if federation?
58
- schema.send(:include, ApolloFederation::Schema)
59
- end
60
-
61
58
  graphiti_schema = GraphitiGraphQL::GraphitiSchema::Wrapper
62
59
  .new(Graphiti::Schema.generate)
63
- # TODO: if we avoid this on federation, or remove altogether
64
- # Make sure we don't blow up
65
- # graphiti_schema.merge_remotes!
66
-
67
- entries = entrypoint_resources || entrypoints
68
- instance.apply_query(graphiti_schema, schema, entries)
69
-
70
- # NB if we add mutation support, make sure this is applied after
71
- if federation?
72
- schema.use GraphQL::Batch
73
- end
74
- instance.schema = schema
75
60
  instance.graphiti_schema = graphiti_schema
61
+ instance.schema = schema
62
+ instance.apply_query(entrypoint_resources || entrypoints)
76
63
  instance
77
64
  end
78
65
 
@@ -82,147 +69,32 @@ module GraphitiGraphQL
82
69
 
83
70
  def initialize
84
71
  @type_registry = {}
72
+ @query_fields = {}
85
73
  end
86
74
 
87
- # TODO put this in a Federation::Schema module
88
- # Maybe even the External classes themselves?
89
- # TODO assign/assign_each
90
- def apply_federation(graphiti_schema, graphql_schema)
91
- type_registry.each_pair do |name, config|
92
- if config[:resource]
93
- local_type = config[:type]
94
- local_resource = Graphiti.resources
95
- .find { |r| r.name == config[:resource] }
96
- # TODO: maybe turn off the graphiti debug for these?
97
- local_type.define_singleton_method :resolve_reference do |reference, context, lookahead|
98
- Federation::BelongsToLoader
99
- .for(local_resource, lookahead.selections.map(&:name))
100
- .load(reference[:id])
101
- end
102
- end
103
- end
104
-
105
- # NB: test already registered bc 2 things have same relationship
106
- GraphitiGraphQL::Federation.external_resources.each_pair do |klass_name, config|
107
- pre_registered = !!type_registry[klass_name]
108
- external_klass = if pre_registered
109
- type_registry[klass_name][:type]
110
- else
111
- external_klass = Class.new(self.class.base_object)
112
- external_klass.graphql_name klass_name
113
- external_klass
114
- end
115
-
116
- unless pre_registered
117
- external_klass.key(fields: "id")
118
- external_klass.extend_type
119
- external_klass.field :id, String, null: false, external: true
120
- external_klass.class_eval do
121
- def self.resolve_reference(reference, _context, _lookup)
122
- reference
123
- end
124
- end
125
- end
126
-
127
- unless pre_registered
128
- # NB must be registered before processing rels
129
- type_registry[klass_name] = { type: external_klass }
130
- end
131
-
132
- # TODO: only do it if field not already defined
133
- config.relationships.each_pair do |name, relationship|
134
- if relationship.has_many?
135
- define_federated_has_many(graphiti_schema, external_klass, relationship)
136
- elsif relationship.belongs_to?
137
- define_federated_belongs_to(config, relationship)
138
- end
139
- end
140
- end
141
- end
142
-
143
- # TODO: refactor to not constantly pass schemas around
144
- def define_federated_has_many(graphiti_schema, external_klass, relationship)
145
- local_name = GraphitiGraphQL::GraphitiSchema::Resource
146
- .gql_name(relationship.local_resource_class.name)
147
- local_type = type_registry[local_name][:type]
148
- local_resource_name = type_registry[local_name][:resource]
149
- local_resource = Graphiti.resources.find { |r| r.name == local_resource_name }
150
-
151
- local_interface = type_registry["I#{local_name}"]
152
- best_type = local_interface ? local_interface[:type] : local_type
153
-
154
- field = external_klass.field relationship.name,
155
- [best_type],
156
- null: false,
157
- extras: [:lookahead]
158
-
159
- define_arguments_for_sideload_field(field, graphiti_schema.get_resource(local_resource_name))
160
- external_klass.define_method relationship.name do |lookahead:, **arguments|
161
- # TODO test params...do version of sort with array/symbol keys and plain string
162
- params = arguments.as_json
163
- .deep_transform_keys { |key| key.to_s.underscore.to_sym }
164
- selections = lookahead.selections.map(&:name)
165
- selections << relationship.foreign_key
166
- selections << :_type # polymorphism
167
- params[:fields] = { local_resource.type => selections.join(",") }
168
-
169
- if (sort = Util.parse_sort(params[:sort]))
170
- params[:sort] = sort
171
- end
172
-
173
- Federation::HasManyLoader
174
- .for(local_resource, params, relationship.foreign_key)
175
- .load(object[:id])
176
- end
177
- end
178
-
179
- def define_federated_belongs_to(external_resource_config, relationship)
180
- type_name = GraphitiSchema::Resource.gql_name(relationship.local_resource_class.name)
181
- local_type = type_registry[type_name][:type]
182
-
183
- # Todo maybe better way here
184
- interface = type_registry["I#{type_name}"]
185
-
186
- local_type = interface[:type] if interface
187
- local_resource_name = type_registry[type_name][:resource]
188
- local_resource_class = Graphiti.resources.find { |r| r.name == local_resource_name }
189
-
190
- local_types = [local_type]
191
- if interface
192
- local_types |= interface[:implementers]
193
- end
194
-
195
- local_types.each do |local|
196
- local.field relationship.name,
197
- type_registry[external_resource_config.type_name][:type], # todo need to define the type?
198
- null: true
199
- end
200
- end
201
-
202
- def apply_query(graphiti_schema, graphql_schema, entries)
203
- query_type = generate_schema_query(graphql_schema, graphiti_schema, entries)
204
- if self.class.federation?
205
- apply_federation(graphiti_schema, schema)
206
- end
75
+ def apply_query(entries)
76
+ query_type = generate_schema_query(entries)
77
+ Federation::SchemaDecorator.decorate(self) if self.class.federation?
207
78
 
208
79
  # NB - don't call .query here of federation will break things
209
- if graphql_schema.instance_variable_get(:@query_object)
210
- graphql_schema.instance_variable_set(:@query_object, nil)
211
- 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)
212
83
  end
213
- graphql_schema.orphan_types(orphans(graphql_schema))
214
- graphql_schema.query(query_type)
215
- 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
216
87
  end
217
88
 
218
- def generate_schema_query(graphql_schema, graphiti_schema, entrypoint_resources = nil)
219
- 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)
220
92
  # NB - don't call graphql_schema.query here of federation will break things
221
93
  query_class = Class.new(existing_query || self.class.base_object)
222
94
  # NB MUST be Query or federation-ruby will break things
223
95
  query_class.graphql_name "Query"
224
96
 
225
- entrypoints(graphiti_schema, entrypoint_resources).each do |resource|
97
+ get_entrypoints(entrypoint_resources).each do |resource|
226
98
  next if resource.remote?
227
99
  generate_type(resource)
228
100
 
@@ -243,28 +115,65 @@ module GraphitiGraphQL
243
115
  end
244
116
  end
245
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
+
246
133
  private
247
134
 
248
- def add_index(query_class, resource)
249
- field = query_class.field resource.graphql_entrypoint,
250
- [type_registry[resource.graphql_class_name][:type]],
135
+ def generate_connection_type(resource, top_level: true)
136
+ name = "#{resource.graphql_class_name}#{top_level ? "TopLevel" : ""}Connection"
137
+ if registered = type_registry[name]
138
+ return registered[:type]
139
+ end
140
+
141
+ type = type_registry[resource.graphql_class_name][:type]
142
+ klass = Class.new(self.class.base_object)
143
+ klass.graphql_name(name)
144
+ klass.field :nodes,
145
+ [type],
251
146
  "List #{resource.graphql_class_name(false).pluralize}",
252
147
  null: false
148
+ if top_level
149
+ klass.field :stats, generate_stat_class(resource), null: false
150
+ end
151
+ register(name, klass)
152
+ klass
153
+ end
154
+
155
+ def add_index(query_class, resource)
156
+ field_name = resource.graphql_entrypoint.to_s.underscore.to_sym
157
+ field = query_class.field field_name,
158
+ generate_connection_type(resource, top_level: true),
159
+ null: false
160
+ @query_fields[field_name] = resource
253
161
  define_arguments_for_sideload_field(field, resource)
254
162
  end
255
163
 
256
164
  def add_show(query_class, resource)
257
- entrypoint = resource.graphql_entrypoint.to_s.singularize.to_sym
258
- field = query_class.field entrypoint,
165
+ field_name = resource.graphql_entrypoint.to_s.underscore.singularize.to_sym
166
+ field = query_class.field field_name,
259
167
  type_registry[resource.graphql_class_name][:type],
260
168
  "Single #{resource.graphql_class_name(false).singularize}",
261
- null: true
169
+ null: false
170
+ @query_fields[field_name] = resource
262
171
  define_arguments_for_sideload_field field,
263
172
  resource,
264
173
  top_level_single: true
265
174
  end
266
175
 
267
- def entrypoints(graphiti_schema, manually_specified)
176
+ def get_entrypoints(manually_specified)
268
177
  resources = graphiti_schema.resources
269
178
  if manually_specified
270
179
  resources = resources.select { |r|
@@ -308,13 +217,17 @@ module GraphitiGraphQL
308
217
  if top_level_single
309
218
  field.argument(:id, String, required: true)
310
219
  else
311
- sort_type = generate_sort_type(resource)
312
- field.argument :sort, [sort_type], required: false
220
+ unless resource.sorts.empty?
221
+ sort_type = generate_sort_type(resource)
222
+ field.argument :sort, [sort_type], required: false
223
+ end
313
224
  field.argument :page, PageType, required: false
314
225
 
315
- filter_type = generate_filter_type(field, resource)
316
- required = resource.filters.any? { |name, config| !!config[:required] }
317
- field.argument :filter, filter_type, required: required
226
+ unless resource.filters.empty?
227
+ filter_type = generate_filter_type(field, resource)
228
+ required = resource.filters.any? { |name, config| !!config[:required] }
229
+ field.argument :filter, filter_type, required: required
230
+ end
318
231
  end
319
232
  end
320
233
 
@@ -338,17 +251,38 @@ module GraphitiGraphQL
338
251
  # TODO guarded operators or otherwise whatever eq => nil is
339
252
  def generate_filter_attribute_type(type_name, filter_name, filter_config)
340
253
  klass = Class.new(GraphQL::Schema::InputObject)
341
- klass.graphql_name "#{type_name}Filter#{filter_name.to_s.camelize(:lower)}"
254
+ filter_graphql_name = "#{type_name}Filter#{filter_name.to_s.camelize(:lower)}"
255
+ klass.graphql_name(filter_graphql_name)
342
256
  filter_config[:operators].each do |operator|
343
257
  canonical_graphiti_type = Graphiti::Types
344
258
  .name_for(filter_config[:type])
345
259
  type = GQL_TYPE_MAP[canonical_graphiti_type]
346
260
  required = !!filter_config[:required] && operator == "eq"
261
+
262
+ if (allowlist = filter_config[:allow])
263
+ type = define_allowlist_type(filter_graphql_name, allowlist)
264
+ end
265
+
266
+ type = [type] unless !!filter_config[:single]
347
267
  klass.argument operator, type, required: required
348
268
  end
349
269
  klass
350
270
  end
351
271
 
272
+ def define_allowlist_type(filter_graphql_name, allowlist)
273
+ name = "#{filter_graphql_name}Allow"
274
+ if (registered = type_registry[name])
275
+ return registered[:type]
276
+ end
277
+ klass = Class.new(GraphQL::Schema::Enum)
278
+ klass.graphql_name(name)
279
+ allowlist.each do |allowed|
280
+ klass.value(allowed)
281
+ end
282
+ register(name, klass)
283
+ klass
284
+ end
285
+
352
286
  def generate_resource_for_sideload(sideload)
353
287
  if sideload.type == :polymorphic_belongs_to
354
288
  unless registered?(sideload.parent_resource)
@@ -375,7 +309,11 @@ module GraphitiGraphQL
375
309
  type_registry[sideload.graphql_class_name][:type]
376
310
  end
377
311
 
378
- gql_field_type = sideload.to_many? ? [gql_type] : gql_type
312
+ gql_field_type = if sideload.to_many?
313
+ generate_connection_type(sideload.resource, top_level: false)
314
+ else
315
+ gql_type
316
+ end
379
317
  field_name = name.to_s.camelize(:lower)
380
318
  unless type_class.fields[field_name]
381
319
  field = type_class.field field_name.to_sym,
@@ -383,7 +321,6 @@ module GraphitiGraphQL
383
321
  null: !sideload.to_many?
384
322
 
385
323
  # No sort/filter/paginate on belongs_to
386
- # unless sideload.type.to_s.include?('belongs_to')
387
324
  unless sideload.type == :polymorphic_belongs_to
388
325
  define_arguments_for_sideload_field(field, sideload.resource)
389
326
  end
@@ -412,8 +349,9 @@ module GraphitiGraphQL
412
349
  klass = Module.new
413
350
  klass.send(:include, self.class.base_interface)
414
351
  klass.definition_methods do
352
+ # rubocop:disable Lint/NestedMethodDefinition(Standard)
415
353
  def resolve_type(object, context)
416
- Graphiti.graphql_schema.schema.types[object[:__typename]]
354
+ GraphitiGraphQL.schemas.graphql.types[object[:__typename]]
417
355
  end
418
356
  end
419
357
  else
@@ -425,10 +363,6 @@ module GraphitiGraphQL
425
363
  implement(klass, type_registry[implements])
426
364
  end
427
365
 
428
- if self.class.federation?
429
- klass.key fields: "id"
430
- end
431
-
432
366
  klass.field(:_type, String, null: false)
433
367
  resource.all_attributes.each do |name, config|
434
368
  if config[:readable]
@@ -482,8 +416,27 @@ module GraphitiGraphQL
482
416
  klass
483
417
  end
484
418
 
419
+ def generate_stat_class(resource)
420
+ klass = Class.new(self.class.base_object)
421
+ klass.graphql_name "#{resource.graphql_class_name(false)}Stats"
422
+ resource.stats.each_pair do |name, calculations|
423
+ calc_class = generate_calc_class(resource, name, calculations)
424
+ klass.field name, calc_class, null: false
425
+ end
426
+ klass
427
+ end
428
+
429
+ def generate_calc_class(resource, stat_name, calculations)
430
+ klass = Class.new(self.class.base_object)
431
+ klass.graphql_name "#{resource.graphql_class_name(false)}#{stat_name}Calculations"
432
+ calculations.each do |calc|
433
+ klass.field calc, Float, null: false
434
+ end
435
+ klass
436
+ end
437
+
485
438
  def registered?(resource)
486
- name = resource.graphql_class_name(false)
439
+ name = resource.graphql_class_name(true)
487
440
  !!type_registry[name]
488
441
  end
489
442