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.
- 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 -34
- data/lib/graphiti_graphql/engine.rb +5 -11
- 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 +120 -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
|
@@ -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
|
-
|
88
|
-
|
89
|
-
|
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
|
210
|
-
|
211
|
-
|
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
|
-
|
214
|
-
|
215
|
-
|
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(
|
219
|
-
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)
|
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
|
-
|
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
|
249
|
-
|
250
|
-
|
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
|
-
|
258
|
-
field = query_class.field
|
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:
|
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
|
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
|
-
|
312
|
-
|
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
|
-
|
316
|
-
|
317
|
-
|
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
|
-
|
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?
|
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
|
-
|
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(
|
439
|
+
name = resource.graphql_class_name(true)
|
487
440
|
!!type_registry[name]
|
488
441
|
end
|
489
442
|
|