graphiti_graphql 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94c56621cb36c796bbefd0e8f626d90e359e2e8740b2cb443a8293e2f98b57df
4
- data.tar.gz: dd1d065a140a292adab0f5d3c54e426dfcfa55b3691523f50ceb947fa752fdbd
3
+ metadata.gz: 75a9873f8fda91fa24f41d06e146826f3de27165452b61202dc45d597e869ef5
4
+ data.tar.gz: 69b67b64c1af770a2df57a74e6a30175e7c80f4d26d4a73a2db958f153b1c5b3
5
5
  SHA512:
6
- metadata.gz: 94886a1ebbed6fee376239b1a10c197c3891cd280aad29973a7a1ce4d6f769e9c1fc3949bb4613dbe98c44d1f584a1b2863955c605798525e24ee1766b136891
7
- data.tar.gz: d0ca0298e0e77da84361a1748a2b36457569910d43aed602a94cfd6eb8869c32c8a0db611bca68dbf64382fe0c5cd8dddb8956c105af9ae7819c4e92cce03adc
6
+ metadata.gz: fe62cbb5d5dd96ae3008add2bd13c465e0f2fd944869a478f544690bf6c459db2daf1504957adb243412a88cd318759fc6cd46f03a436b7b428d912d2530d2be
7
+ data.tar.gz: 2bd729f975995cf7433a3fc7c6864586dd5515955c68941c91e6036d2ef54cd024a3508a0869ed67eda7d6e61f612c37ae1b9b9b13ef19bb636948629edb4222
data/Gemfile CHANGED
@@ -1,6 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in graphiti_graphql.gemspec
4
- gemspec
5
-
6
- gem "graphiti"
4
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,19 +1,9 @@
1
- PATH
2
- remote: ../graphiti
3
- specs:
4
- graphiti (1.2.32)
5
- activesupport (>= 4.1)
6
- concurrent-ruby (~> 1.0)
7
- dry-types (>= 0.15.0, < 2.0)
8
- graphiti_errors (~> 1.1.0)
9
- jsonapi-renderer (~> 0.2, >= 0.2.2)
10
- jsonapi-serializable (~> 0.3.0)
11
-
12
1
  PATH
13
2
  remote: .
14
3
  specs:
15
- graphiti_graphql (0.1.1)
4
+ graphiti_graphql (0.1.2)
16
5
  activesupport (>= 4.1)
6
+ graphiti (>= 1.2.36)
17
7
  graphql (~> 1.12)
18
8
 
19
9
  GEM
@@ -54,6 +44,13 @@ GEM
54
44
  dry-inflector (~> 0.1, >= 0.1.2)
55
45
  dry-logic (~> 1.0, >= 1.0.2)
56
46
  google-protobuf (3.15.2)
47
+ graphiti (1.2.36)
48
+ activesupport (>= 4.1)
49
+ concurrent-ruby (~> 1.0)
50
+ dry-types (>= 0.15.0, < 2.0)
51
+ graphiti_errors (~> 1.1.0)
52
+ jsonapi-renderer (~> 0.2, >= 0.2.2)
53
+ jsonapi-serializable (~> 0.3.0)
57
54
  graphiti_errors (1.1.2)
58
55
  jsonapi-serializable (~> 0.1)
59
56
  graphiti_spec_helpers (1.0.5)
@@ -63,13 +60,13 @@ GEM
63
60
  graphql-batch (0.4.3)
64
61
  graphql (>= 1.3, < 2)
65
62
  promise.rb (~> 0.7.2)
66
- i18n (1.8.7)
63
+ i18n (1.8.9)
67
64
  concurrent-ruby (~> 1.0)
68
65
  jsonapi-renderer (0.2.2)
69
66
  jsonapi-serializable (0.3.1)
70
67
  jsonapi-renderer (~> 0.2.0)
71
68
  method_source (1.0.0)
72
- minitest (5.14.3)
69
+ minitest (5.14.4)
73
70
  parallel (1.20.1)
74
71
  parser (3.0.0.0)
75
72
  ast (~> 2.4.1)
@@ -129,7 +126,6 @@ DEPENDENCIES
129
126
  activemodel (>= 4.1)
130
127
  apollo-federation (~> 1.1)
131
128
  bundler (~> 2.0)
132
- graphiti!
133
129
  graphiti_graphql!
134
130
  graphiti_spec_helpers
135
131
  graphql (~> 1.12)
data/README.md CHANGED
@@ -4,12 +4,14 @@ GraphQL (and Apollo Federation) support for Graphiti. Serve traditional Rails JS
4
4
 
5
5
  Currently read-only, but you can add your own Mutations [manually](#blending-with-graphql-ruby).
6
6
 
7
+ Pre-alpha. Stay tuned.
8
+
7
9
  ## Setup
8
10
 
9
11
  Add to your `Gemfile`:
10
12
 
11
13
  ```rb
12
- gem 'graphiti', ">= 1.2.32"
14
+ gem 'graphiti', ">= 1.2.33"
13
15
  gem "graphiti_graphql"
14
16
  ```
15
17
 
@@ -59,7 +61,7 @@ Define your Schema and Type classes as normal. Then in an initializer:
59
61
  GraphitiGraphQL.schema_class = MySchema
60
62
  ```
61
63
 
62
- Any pre-existing GraphQL endpoint will continue working as normal. But the GQL endpoint you mounted in `config/routes.rb` will now serve BOTH your low-level `graphql-ruby` schema AND your Graphiti-specific schema. Note these cannot (currently) be served side-by-side on under `query` within the *same request*.
64
+ Any pre-existing GraphQL endpoint will continue working as normal. But the GQL endpoint you mounted in `config/routes.rb` will now serve BOTH your low-level `graphql-ruby` schema AND your Graphiti-specific schema. Note these cannot (currently) be served side-by-side under `query` within the *same request*.
63
65
 
64
66
  By default the GraphQL context will be `Graphiti.context[:object]`, which is the controller being called. You might want to customize this so your existing graphql-ruby code continues to expect the same context:
65
67
 
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_dependency "graphiti", ">= 1.2.33"
30
+ spec.add_dependency "graphiti", ">= 1.2.36"
31
31
  spec.add_dependency "activesupport", ">= 4.1"
32
32
  spec.add_dependency "graphql", "~> 1.12"
33
33
 
@@ -90,6 +90,22 @@ module GraphitiGraphQL
90
90
  federated_type
91
91
  end
92
92
 
93
+ def define_connection_type(name, type_class)
94
+ name = "#{name}FederatedConnection"
95
+ if (registered = type_registry[name])
96
+ return registered[:type]
97
+ end
98
+
99
+ klass = Class.new(@schema.class.base_object)
100
+ klass.graphql_name(name)
101
+ klass.field :nodes,
102
+ [type_class],
103
+ null: false,
104
+ extras: [:lookahead]
105
+ @schema.send :register, name, klass
106
+ klass
107
+ end
108
+
93
109
  def define_federated_has_many(type_class, relationship)
94
110
  local_name = GraphitiGraphQL::GraphitiSchema::Resource
95
111
  .gql_name(relationship.local_resource_class.name)
@@ -100,16 +116,20 @@ module GraphitiGraphQL
100
116
  local_interface = type_registry["I#{local_name}"]
101
117
  best_type = local_interface ? local_interface[:type] : local_type
102
118
 
119
+ connection_type = define_connection_type(local_name, best_type)
120
+
103
121
  field = type_class.field relationship.name,
104
- [best_type],
122
+ connection_type,
105
123
  null: false,
106
- extras: [:lookahead]
107
-
124
+ connection: false
108
125
  @schema.send :define_arguments_for_sideload_field,
109
126
  field, @schema.graphiti_schema.get_resource(local_resource_name)
110
- type_class.define_method relationship.name do |lookahead:, **arguments|
111
- # TODO test params...do version of sort with array/symbol keys and plain string
112
- params = arguments.as_json
127
+
128
+ type_class.define_method relationship.name do |**arguments|
129
+ {data: object, arguments: arguments}
130
+ end
131
+ connection_type.define_method :nodes do |lookahead:, **arguments|
132
+ params = object[:arguments].as_json
113
133
  .deep_transform_keys { |key| key.to_s.underscore.to_sym }
114
134
  selections = lookahead.selections.map(&:name)
115
135
  selections << relationship.foreign_key
@@ -122,7 +142,7 @@ module GraphitiGraphQL
122
142
 
123
143
  Federation::Loaders::HasMany
124
144
  .for(relationship, params)
125
- .load(object[:id])
145
+ .load(object[:data][:id])
126
146
  end
127
147
  end
128
148
 
@@ -63,6 +63,10 @@ module GraphitiGraphQL
63
63
  config[:name]
64
64
  end
65
65
 
66
+ def stats
67
+ config[:stats]
68
+ end
69
+
66
70
  def type
67
71
  config[:type]
68
72
  end
@@ -38,7 +38,7 @@ module GraphitiGraphQL
38
38
 
39
39
  def render(json, selection_name)
40
40
  payload = if find_one?(selection_name)
41
- {selection_name.to_sym => json.values[0][0]}
41
+ {selection_name.to_sym => json.values[0][:nodes][0]}
42
42
  else
43
43
  json
44
44
  end
@@ -108,15 +108,19 @@ module GraphitiGraphQL
108
108
  gather_filters(params, selection, variables_hash, chained_name)
109
109
  gather_sorts(params, selection, variables_hash, chained_name)
110
110
  gather_pages(params, selection, variables_hash, chained_name)
111
+ gather_stats(params, selection, variables_hash, chained_name)
111
112
  end
112
113
 
113
114
  params[:include] ||= []
114
115
  params[:include] << chained_name if chained_name
115
116
 
116
- 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|
117
121
  s.is_a?(GraphQL::Language::Nodes::InlineFragment)
118
122
  }
119
- non_fragments = selection.selections - fragments
123
+ non_fragments = children - fragments
120
124
 
121
125
  if pbt
122
126
  # Only id/_type possible here
@@ -223,7 +227,9 @@ module GraphitiGraphQL
223
227
 
224
228
  attr_arg.value.arguments.each do |operator_arg|
225
229
  value = operator_arg.value
226
- 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
227
233
  value = variable_hash[operator_arg.value.name]
228
234
  end
229
235
  f[filter_param_name] = {operator_arg.name.underscore => value}
@@ -299,5 +305,22 @@ module GraphitiGraphQL
299
305
  params[:page].merge!(pages)
300
306
  end
301
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
302
325
  end
303
326
  end
@@ -132,11 +132,30 @@ module GraphitiGraphQL
132
132
 
133
133
  private
134
134
 
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],
146
+ "List #{resource.graphql_class_name(false).pluralize}",
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
+
135
155
  def add_index(query_class, resource)
136
156
  field_name = resource.graphql_entrypoint.to_s.underscore.to_sym
137
157
  field = query_class.field field_name,
138
- [type_registry[resource.graphql_class_name][:type]],
139
- "List #{resource.graphql_class_name(false).pluralize}",
158
+ generate_connection_type(resource, top_level: true),
140
159
  null: false
141
160
  @query_fields[field_name] = resource
142
161
  define_arguments_for_sideload_field(field, resource)
@@ -147,7 +166,7 @@ module GraphitiGraphQL
147
166
  field = query_class.field field_name,
148
167
  type_registry[resource.graphql_class_name][:type],
149
168
  "Single #{resource.graphql_class_name(false).singularize}",
150
- null: true
169
+ null: false
151
170
  @query_fields[field_name] = resource
152
171
  define_arguments_for_sideload_field field,
153
172
  resource,
@@ -232,17 +251,38 @@ module GraphitiGraphQL
232
251
  # TODO guarded operators or otherwise whatever eq => nil is
233
252
  def generate_filter_attribute_type(type_name, filter_name, filter_config)
234
253
  klass = Class.new(GraphQL::Schema::InputObject)
235
- 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)
236
256
  filter_config[:operators].each do |operator|
237
257
  canonical_graphiti_type = Graphiti::Types
238
258
  .name_for(filter_config[:type])
239
259
  type = GQL_TYPE_MAP[canonical_graphiti_type]
240
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]
241
267
  klass.argument operator, type, required: required
242
268
  end
243
269
  klass
244
270
  end
245
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
+
246
286
  def generate_resource_for_sideload(sideload)
247
287
  if sideload.type == :polymorphic_belongs_to
248
288
  unless registered?(sideload.parent_resource)
@@ -269,7 +309,11 @@ module GraphitiGraphQL
269
309
  type_registry[sideload.graphql_class_name][:type]
270
310
  end
271
311
 
272
- 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
273
317
  field_name = name.to_s.camelize(:lower)
274
318
  unless type_class.fields[field_name]
275
319
  field = type_class.field field_name.to_sym,
@@ -277,7 +321,6 @@ module GraphitiGraphQL
277
321
  null: !sideload.to_many?
278
322
 
279
323
  # No sort/filter/paginate on belongs_to
280
- # unless sideload.type.to_s.include?('belongs_to')
281
324
  unless sideload.type == :polymorphic_belongs_to
282
325
  define_arguments_for_sideload_field(field, sideload.resource)
283
326
  end
@@ -373,6 +416,25 @@ module GraphitiGraphQL
373
416
  klass
374
417
  end
375
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
+
376
438
  def registered?(resource)
377
439
  name = resource.graphql_class_name(false)
378
440
  !!type_registry[name]
@@ -1,3 +1,3 @@
1
1
  module GraphitiGraphQL
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti_graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
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-07 00:00:00.000000000 Z
11
+ date: 2021-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphiti
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.2.33
19
+ version: 1.2.36
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.2.33
26
+ version: 1.2.36
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement