graphiti_graphql 0.1.1 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -3
- data/Gemfile.lock +14 -18
- data/README.md +141 -15
- data/graphiti_graphql.gemspec +1 -0
- data/lib/graphiti_graphql.rb +49 -32
- data/lib/graphiti_graphql/engine.rb +4 -13
- data/lib/graphiti_graphql/federation.rb +12 -220
- data/lib/graphiti_graphql/federation/apollo_federation_override.rb +54 -0
- data/lib/graphiti_graphql/federation/federated_relationship.rb +26 -0
- data/lib/graphiti_graphql/federation/federated_resource.rb +38 -0
- data/lib/graphiti_graphql/federation/loaders/belongs_to.rb +22 -0
- data/lib/graphiti_graphql/federation/loaders/has_many.rb +33 -0
- data/lib/graphiti_graphql/federation/resource_dsl.rb +101 -0
- data/lib/graphiti_graphql/federation/schema_decorator.rb +190 -0
- data/lib/graphiti_graphql/graphiti_schema/resource.rb +4 -18
- data/lib/graphiti_graphql/graphiti_schema/wrapper.rb +0 -15
- data/lib/graphiti_graphql/runner.rb +42 -29
- data/lib/graphiti_graphql/schema.rb +119 -167
- data/lib/graphiti_graphql/util.rb +1 -1
- data/lib/graphiti_graphql/version.rb +1 -1
- metadata +24 -3
@@ -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
|
-
|
7
|
+
is_graphiti = schemas.generated.query_field?(selection.name)
|
8
8
|
|
9
|
-
#
|
9
|
+
# Wrap *everything* in context, in case of federated request
|
10
10
|
Util.with_gql_context do
|
11
|
-
if
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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 =
|
150
|
-
klass =
|
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.
|
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
|
@@ -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
|
-
|
89
|
-
|
90
|
-
|
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
|
211
|
-
|
212
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
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(
|
220
|
-
existing_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
|
-
|
97
|
+
get_entrypoints(entrypoint_resources).each do |resource|
|
227
98
|
next if resource.remote?
|
228
99
|
generate_type(resource)
|
229
100
|
|
@@ -244,28 +115,65 @@ 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
|
-
def
|
250
|
-
|
251
|
-
|
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],
|
252
146
|
"List #{resource.graphql_class_name(false).pluralize}",
|
253
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
|
254
161
|
define_arguments_for_sideload_field(field, resource)
|
255
162
|
end
|
256
163
|
|
257
164
|
def add_show(query_class, resource)
|
258
|
-
|
259
|
-
field = query_class.field
|
165
|
+
field_name = resource.graphql_entrypoint.to_s.underscore.singularize.to_sym
|
166
|
+
field = query_class.field field_name,
|
260
167
|
type_registry[resource.graphql_class_name][:type],
|
261
168
|
"Single #{resource.graphql_class_name(false).singularize}",
|
262
|
-
null:
|
169
|
+
null: false
|
170
|
+
@query_fields[field_name] = resource
|
263
171
|
define_arguments_for_sideload_field field,
|
264
172
|
resource,
|
265
173
|
top_level_single: true
|
266
174
|
end
|
267
175
|
|
268
|
-
def
|
176
|
+
def get_entrypoints(manually_specified)
|
269
177
|
resources = graphiti_schema.resources
|
270
178
|
if manually_specified
|
271
179
|
resources = resources.select { |r|
|
@@ -309,13 +217,17 @@ module GraphitiGraphQL
|
|
309
217
|
if top_level_single
|
310
218
|
field.argument(:id, String, required: true)
|
311
219
|
else
|
312
|
-
|
313
|
-
|
220
|
+
unless resource.sorts.empty?
|
221
|
+
sort_type = generate_sort_type(resource)
|
222
|
+
field.argument :sort, [sort_type], required: false
|
223
|
+
end
|
314
224
|
field.argument :page, PageType, required: false
|
315
225
|
|
316
|
-
|
317
|
-
|
318
|
-
|
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
|
319
231
|
end
|
320
232
|
end
|
321
233
|
|
@@ -339,17 +251,38 @@ module GraphitiGraphQL
|
|
339
251
|
# TODO guarded operators or otherwise whatever eq => nil is
|
340
252
|
def generate_filter_attribute_type(type_name, filter_name, filter_config)
|
341
253
|
klass = Class.new(GraphQL::Schema::InputObject)
|
342
|
-
|
254
|
+
filter_graphql_name = "#{type_name}Filter#{filter_name.to_s.camelize(:lower)}"
|
255
|
+
klass.graphql_name(filter_graphql_name)
|
343
256
|
filter_config[:operators].each do |operator|
|
344
257
|
canonical_graphiti_type = Graphiti::Types
|
345
258
|
.name_for(filter_config[:type])
|
346
259
|
type = GQL_TYPE_MAP[canonical_graphiti_type]
|
347
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]
|
348
267
|
klass.argument operator, type, required: required
|
349
268
|
end
|
350
269
|
klass
|
351
270
|
end
|
352
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
|
+
|
353
286
|
def generate_resource_for_sideload(sideload)
|
354
287
|
if sideload.type == :polymorphic_belongs_to
|
355
288
|
unless registered?(sideload.parent_resource)
|
@@ -376,7 +309,11 @@ module GraphitiGraphQL
|
|
376
309
|
type_registry[sideload.graphql_class_name][:type]
|
377
310
|
end
|
378
311
|
|
379
|
-
gql_field_type = sideload.to_many?
|
312
|
+
gql_field_type = if sideload.to_many?
|
313
|
+
generate_connection_type(sideload.resource, top_level: false)
|
314
|
+
else
|
315
|
+
gql_type
|
316
|
+
end
|
380
317
|
field_name = name.to_s.camelize(:lower)
|
381
318
|
unless type_class.fields[field_name]
|
382
319
|
field = type_class.field field_name.to_sym,
|
@@ -384,7 +321,6 @@ module GraphitiGraphQL
|
|
384
321
|
null: !sideload.to_many?
|
385
322
|
|
386
323
|
# No sort/filter/paginate on belongs_to
|
387
|
-
# unless sideload.type.to_s.include?('belongs_to')
|
388
324
|
unless sideload.type == :polymorphic_belongs_to
|
389
325
|
define_arguments_for_sideload_field(field, sideload.resource)
|
390
326
|
end
|
@@ -413,8 +349,9 @@ module GraphitiGraphQL
|
|
413
349
|
klass = Module.new
|
414
350
|
klass.send(:include, self.class.base_interface)
|
415
351
|
klass.definition_methods do
|
352
|
+
# rubocop:disable Lint/NestedMethodDefinition(Standard)
|
416
353
|
def resolve_type(object, context)
|
417
|
-
|
354
|
+
GraphitiGraphQL.schemas.graphql.types[object[:__typename]]
|
418
355
|
end
|
419
356
|
end
|
420
357
|
else
|
@@ -426,10 +363,6 @@ module GraphitiGraphQL
|
|
426
363
|
implement(klass, type_registry[implements])
|
427
364
|
end
|
428
365
|
|
429
|
-
if self.class.federation?
|
430
|
-
klass.key fields: "id"
|
431
|
-
end
|
432
|
-
|
433
366
|
klass.field(:_type, String, null: false)
|
434
367
|
resource.all_attributes.each do |name, config|
|
435
368
|
if config[:readable]
|
@@ -483,8 +416,27 @@ module GraphitiGraphQL
|
|
483
416
|
klass
|
484
417
|
end
|
485
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
|
+
|
486
438
|
def registered?(resource)
|
487
|
-
name = resource.graphql_class_name(
|
439
|
+
name = resource.graphql_class_name(true)
|
488
440
|
!!type_registry[name]
|
489
441
|
end
|
490
442
|
|