rails-graphql 1.0.0.beta → 1.0.0.rc2

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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/ext/gql_parser.c +1 -16
  3. data/ext/gql_parser.h +21 -0
  4. data/ext/shared.c +0 -5
  5. data/ext/shared.h +6 -6
  6. data/lib/generators/graphql/channel_generator.rb +27 -0
  7. data/lib/generators/graphql/controller_generator.rb +9 -4
  8. data/lib/generators/graphql/install_generator.rb +49 -0
  9. data/lib/generators/graphql/schema_generator.rb +9 -4
  10. data/lib/generators/graphql/templates/channel.erb +7 -0
  11. data/lib/generators/graphql/templates/config.rb +97 -0
  12. data/lib/generators/graphql/templates/controller.erb +2 -0
  13. data/lib/generators/graphql/templates/schema.erb +5 -3
  14. data/lib/gql_parser.so +0 -0
  15. data/lib/rails/graphql/alternative/field_set.rb +12 -0
  16. data/lib/rails/graphql/alternative/query.rb +13 -8
  17. data/lib/rails/graphql/alternative/subscription.rb +2 -1
  18. data/lib/rails/graphql/alternative.rb +4 -0
  19. data/lib/rails/graphql/argument.rb +5 -3
  20. data/lib/rails/graphql/callback.rb +10 -8
  21. data/lib/rails/graphql/collectors/hash_collector.rb +12 -1
  22. data/lib/rails/graphql/collectors/json_collector.rb +21 -0
  23. data/lib/rails/graphql/config.rb +86 -59
  24. data/lib/rails/graphql/directive/include_directive.rb +0 -1
  25. data/lib/rails/graphql/directive/skip_directive.rb +0 -1
  26. data/lib/rails/graphql/directive/specified_by_directive.rb +24 -0
  27. data/lib/rails/graphql/directive.rb +31 -25
  28. data/lib/rails/graphql/event.rb +7 -6
  29. data/lib/rails/graphql/field/authorized_field.rb +0 -5
  30. data/lib/rails/graphql/field/input_field.rb +0 -5
  31. data/lib/rails/graphql/field/mutation_field.rb +5 -6
  32. data/lib/rails/graphql/field/output_field.rb +13 -2
  33. data/lib/rails/graphql/field/proxied_field.rb +6 -6
  34. data/lib/rails/graphql/field/resolved_field.rb +1 -1
  35. data/lib/rails/graphql/field/subscription_field.rb +35 -52
  36. data/lib/rails/graphql/field/typed_field.rb +26 -2
  37. data/lib/rails/graphql/field.rb +20 -19
  38. data/lib/rails/graphql/global_id.rb +5 -1
  39. data/lib/rails/graphql/helpers/inherited_collection/array.rb +1 -0
  40. data/lib/rails/graphql/helpers/inherited_collection/base.rb +3 -1
  41. data/lib/rails/graphql/helpers/inherited_collection/hash.rb +2 -1
  42. data/lib/rails/graphql/helpers/registerable.rb +1 -1
  43. data/lib/rails/graphql/helpers/with_arguments.rb +3 -2
  44. data/lib/rails/graphql/helpers/with_assignment.rb +5 -5
  45. data/lib/rails/graphql/helpers/with_callbacks.rb +3 -3
  46. data/lib/rails/graphql/helpers/with_description.rb +10 -8
  47. data/lib/rails/graphql/helpers/with_directives.rb +5 -1
  48. data/lib/rails/graphql/helpers/with_events.rb +1 -0
  49. data/lib/rails/graphql/helpers/with_fields.rb +30 -24
  50. data/lib/rails/graphql/helpers/with_name.rb +3 -2
  51. data/lib/rails/graphql/helpers/with_schema_fields.rb +75 -51
  52. data/lib/rails/graphql/introspection.rb +1 -1
  53. data/lib/rails/graphql/railtie.rb +3 -2
  54. data/lib/rails/graphql/railties/app/base_channel.rb +10 -0
  55. data/lib/rails/graphql/railties/app/base_controller.rb +12 -0
  56. data/lib/rails/graphql/railties/app/views/_cable.js.erb +56 -0
  57. data/lib/rails/graphql/railties/app/views/_fetch.js.erb +20 -0
  58. data/lib/rails/graphql/railties/app/views/graphiql.html.erb +101 -0
  59. data/lib/rails/graphql/railties/base_generator.rb +3 -9
  60. data/lib/rails/graphql/railties/channel.rb +8 -8
  61. data/lib/rails/graphql/railties/controller.rb +51 -26
  62. data/lib/rails/graphql/request/arguments.rb +2 -1
  63. data/lib/rails/graphql/request/backtrace.rb +31 -10
  64. data/lib/rails/graphql/request/component/field.rb +15 -8
  65. data/lib/rails/graphql/request/component/fragment.rb +13 -7
  66. data/lib/rails/graphql/request/component/operation/subscription.rb +4 -6
  67. data/lib/rails/graphql/request/component/operation.rb +12 -5
  68. data/lib/rails/graphql/request/component/spread.rb +13 -4
  69. data/lib/rails/graphql/request/component/typename.rb +1 -1
  70. data/lib/rails/graphql/request/component.rb +2 -0
  71. data/lib/rails/graphql/request/context.rb +1 -1
  72. data/lib/rails/graphql/request/event.rb +6 -2
  73. data/lib/rails/graphql/request/helpers/directives.rb +1 -0
  74. data/lib/rails/graphql/request/helpers/selection_set.rb +10 -4
  75. data/lib/rails/graphql/request/helpers/value_writers.rb +8 -5
  76. data/lib/rails/graphql/request/prepared_data.rb +3 -1
  77. data/lib/rails/graphql/request/steps/organizable.rb +1 -1
  78. data/lib/rails/graphql/request/steps/preparable.rb +1 -1
  79. data/lib/rails/graphql/request/steps/resolvable.rb +1 -1
  80. data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +3 -3
  81. data/lib/rails/graphql/request/strategy.rb +18 -4
  82. data/lib/rails/graphql/request/subscription.rb +18 -16
  83. data/lib/rails/graphql/request.rb +71 -41
  84. data/lib/rails/graphql/schema.rb +39 -86
  85. data/lib/rails/graphql/shortcuts.rb +11 -5
  86. data/lib/rails/graphql/source/active_record/builders.rb +22 -24
  87. data/lib/rails/graphql/source/active_record_source.rb +96 -34
  88. data/lib/rails/graphql/source/base.rb +13 -40
  89. data/lib/rails/graphql/source/builder.rb +14 -22
  90. data/lib/rails/graphql/source/scoped_arguments.rb +10 -4
  91. data/lib/rails/graphql/source.rb +31 -38
  92. data/lib/rails/graphql/subscription/provider/action_cable.rb +10 -9
  93. data/lib/rails/graphql/subscription/provider/base.rb +6 -5
  94. data/lib/rails/graphql/subscription/store/base.rb +5 -9
  95. data/lib/rails/graphql/subscription/store/memory.rb +18 -9
  96. data/lib/rails/graphql/type/creator.rb +198 -0
  97. data/lib/rails/graphql/type/enum.rb +17 -9
  98. data/lib/rails/graphql/type/input.rb +30 -7
  99. data/lib/rails/graphql/type/interface.rb +15 -4
  100. data/lib/rails/graphql/type/object/directive_object.rb +6 -5
  101. data/lib/rails/graphql/type/object/input_value_object.rb +3 -4
  102. data/lib/rails/graphql/type/object/type_object.rb +40 -13
  103. data/lib/rails/graphql/type/object.rb +11 -6
  104. data/lib/rails/graphql/type/scalar/binary_scalar.rb +2 -0
  105. data/lib/rails/graphql/type/scalar/date_scalar.rb +2 -0
  106. data/lib/rails/graphql/type/scalar/date_time_scalar.rb +2 -0
  107. data/lib/rails/graphql/type/scalar/decimal_scalar.rb +2 -0
  108. data/lib/rails/graphql/type/scalar/json_scalar.rb +3 -1
  109. data/lib/rails/graphql/type/scalar/time_scalar.rb +3 -1
  110. data/lib/rails/graphql/type/scalar.rb +2 -2
  111. data/lib/rails/graphql/type/union.rb +7 -2
  112. data/lib/rails/graphql/type.rb +10 -2
  113. data/lib/rails/graphql/type_map.rb +20 -7
  114. data/lib/rails/graphql/uri.rb +5 -4
  115. data/lib/rails/graphql/version.rb +6 -2
  116. data/lib/rails/graphql.rb +11 -8
  117. data/test/assets/introspection-mem.txt +1 -1
  118. data/test/assets/introspection.gql +2 -0
  119. data/test/assets/mem.gql +74 -60
  120. data/test/assets/mysql.gql +69 -55
  121. data/test/assets/sqlite.gql +78 -64
  122. data/test/assets/translate.gql +50 -39
  123. data/test/config.rb +2 -1
  124. data/test/graphql/schema_test.rb +2 -31
  125. data/test/graphql/source_test.rb +1 -11
  126. data/test/graphql/type/interface_test.rb +8 -5
  127. data/test/graphql/type/object_test.rb +8 -2
  128. data/test/graphql/type_map_test.rb +13 -16
  129. data/test/integration/global_id_test.rb +4 -4
  130. data/test/integration/memory/star_wars_validation_test.rb +2 -2
  131. data/test/integration/mysql/star_wars_introspection_test.rb +1 -1
  132. data/test/integration/resolver_precedence_test.rb +1 -1
  133. data/test/integration/schemas/memory.rb +3 -4
  134. data/test/integration/sqlite/star_wars_global_id_test.rb +27 -21
  135. data/test/integration/sqlite/star_wars_introspection_test.rb +1 -1
  136. data/test/integration/translate_test.rb +26 -14
  137. metadata +22 -9
@@ -17,21 +17,24 @@ module Rails
17
17
  def inherited(subclass)
18
18
  super if defined? super
19
19
 
20
- TYPE_FIELD_CLASS.each_key do |kind|
21
- fields = instance_variable_defined?("@#{kind}_fields")
22
- fields = fields ? instance_variable_get("@#{kind}_fields") : {}
23
- fields.each_value { |field| subclass.add_proxy_field(kind, field) }
20
+ TYPE_FIELD_CLASS.each_key do |type|
21
+ fields = instance_variable_defined?("@#{type}_fields")
22
+ fields = fields ? instance_variable_get("@#{type}_fields") : EMPTY_HASH
23
+ fields.each_value { |field| subclass.add_proxy_field(type, field) }
24
24
  end
25
25
  end
26
26
  end
27
27
 
28
28
  # Helper class to be used as the +self+ in configuration blocks
29
29
  ScopedConfig = Struct.new(:source, :type) do
30
- def arg(*args, **xargs, &block)
30
+ def argument(*args, **xargs, &block)
31
31
  xargs[:owner] ||= source
32
32
  GraphQL::Argument.new(*args, **xargs, &block)
33
33
  end
34
34
 
35
+ alias arg argument
36
+ alias kind type
37
+
35
38
  private
36
39
 
37
40
  def respond_to_missing?(method_name, include_private = false)
@@ -55,7 +58,6 @@ module Rails
55
58
  safe_field: :safe_add_field,
56
59
  field: :add_field,
57
60
  proxy_field: :add_proxy_field,
58
- field?: :has_field?,
59
61
  import: :import_into,
60
62
  import_all: :import_all_into,
61
63
  )
@@ -76,13 +78,16 @@ module Rails
76
78
  end
77
79
  end
78
80
 
81
+ # Allow hash access with the type or the type and the name
82
+ def [](type, name = nil)
83
+ name.nil? ? fields_for(type) : find_field(type, name)
84
+ end
85
+
79
86
  # Check if there are fields set fot he given type
80
87
  def fields_for?(type)
81
88
  public_send("#{type}_fields?")
82
89
  end
83
90
 
84
- alias [] :fields_for
85
-
86
91
  # Return the object name for a given +type+ of list of fields
87
92
  def type_name_for(type)
88
93
  method_name = :"#{type}_type_name"
@@ -100,7 +105,7 @@ module Rails
100
105
  # Add a new field of the give +type+
101
106
  # See {OutputField}[rdoc-ref:Rails::GraphQL::OutputField] class.
102
107
  def add_field(type, *args, **xargs, &block)
103
- klass = Field.const_get(TYPE_FIELD_CLASS[type])
108
+ klass = Field.const_get(TYPE_FIELD_CLASS[type], false)
104
109
  object = klass.new(*args, **xargs, owner: self, &block)
105
110
 
106
111
  raise DuplicatedError, (+<<~MSG).squish if has_field?(type, object.name)
@@ -116,11 +121,12 @@ module Rails
116
121
  # Add a new field to the list but use a proxy instead of a hard copy of
117
122
  # a given +field+
118
123
  def add_proxy_field(type, field, *args, **xargs, &block)
124
+ field = field.field if field.is_a?(Module) && field <= Alternative::Query
119
125
  raise ArgumentError, (+<<~MSG).squish if field.schema_type != type
120
126
  A #{field.schema_type} field cannot be added as a #{type} field.
121
127
  MSG
122
128
 
123
- klass = Field.const_get(TYPE_FIELD_CLASS[type])
129
+ klass = Field.const_get(TYPE_FIELD_CLASS[type], false)
124
130
  raise ArgumentError, (+<<~MSG).squish unless field.is_a?(klass)
125
131
  The #{field.class.name} is not a valid field for #{type} fields.
126
132
  MSG
@@ -180,12 +186,15 @@ module Rails
180
186
  MSG
181
187
  end
182
188
 
183
- # Get the list of GraphQL names of all the fields difined
189
+ # Get the list of GraphQL names of all the fields defined
184
190
  def field_names_for(type, enabled_only = true)
185
- return unless fields_for?(type)
186
- list = fields_for(type)
187
- list = list.select(&:enabled?) if enabled_only
188
- list.map(&:gql_name).compact
191
+ source = (enabled_only ? enabled_fields_from(type) : lazy_each_field_from(type))
192
+ source&.map(&:gql_name)&.eager
193
+ end
194
+
195
+ # Return a lazy enumerator for enabled fields
196
+ def enabled_fields_from(type)
197
+ lazy_each_field_from(type)&.select(&:enabled?)
189
198
  end
190
199
 
191
200
  # Run a configuration block for the given +type+
@@ -195,8 +204,6 @@ module Rails
195
204
 
196
205
  # Import a class of fields into the given section of schema fields
197
206
  def import_into(type, source)
198
- return if source.try(:abstract?)
199
-
200
207
  # Import an alternative declaration of a field
201
208
  if source.is_a?(Module) && source <= Alternative::Query
202
209
  return add_proxy_field(type, source.field)
@@ -229,10 +236,10 @@ module Rails
229
236
  # TODO: Maybe add deepness into the recursive value
230
237
  def import_all_into(type, mod, recursive: false, **xargs)
231
238
  mod.constants.each do |const_name|
232
- object = mod.const_get(const_name)
239
+ object = mod.const_get(const_name, false)
233
240
 
234
241
  import_into(type, object, **xargs) if object.is_a?(Class)
235
- import_all_into(type, object, recursive: recursive, **xargs) if recursive
242
+ import_all_into(type, object, recursive: recursive, **xargs) if recursive && object.is_a?(Module)
236
243
  end
237
244
  end
238
245
 
@@ -253,9 +260,9 @@ module Rails
253
260
  def validate!(*)
254
261
  super if defined? super
255
262
 
256
- TYPE_FIELD_CLASS.each_key do |kind|
257
- next unless public_send("#{kind}_fields?")
258
- fields_for(kind).each_value(&:validate!)
263
+ TYPE_FIELD_CLASS.each_key do |type|
264
+ next unless public_send("#{type}_fields?")
265
+ fields_for(type).each_value(&:validate!)
259
266
  end
260
267
  end
261
268
 
@@ -264,52 +271,69 @@ module Rails
264
271
  find_field!(gid.scope, gid.name)
265
272
  end
266
273
 
267
- TYPE_FIELD_CLASS.each_key do |kind|
274
+ TYPE_FIELD_CLASS.each_key do |type|
268
275
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
269
- def #{kind}_field?(name)
270
- has_field?(:#{kind}, name)
276
+ def #{type}_fields?
277
+ defined?(@#{type}_fields) && @#{type}_fields.present?
271
278
  end
272
279
 
273
- def #{kind}_field(name)
274
- find_field(:#{kind}, name)
280
+ def #{type}_fields(&block)
281
+ configure_fields(:#{type}, &block) if block.present?
282
+ @#{type}_fields if defined?(@#{type}_fields)
275
283
  end
276
284
 
277
- def add_#{kind}_field(*args, **xargs, &block)
278
- add_field(:#{kind}, *args, **xargs, &block)
285
+ def add_#{type}_field(*args, **xargs, &block)
286
+ add_field(:#{type}, *args, **xargs, &block)
279
287
  end
280
288
 
281
- def #{kind}_fields?
282
- defined?(@#{kind}_fields) && @#{kind}_fields.present?
289
+ def #{type}_field?(name)
290
+ has_field?(:#{type}, name)
283
291
  end
284
292
 
285
- def #{kind}_fields(&block)
286
- configure_fields(:#{kind}, &block) if block.present?
287
- @#{kind}_fields if defined?(@#{kind}_fields)
293
+ def #{type}_field(name)
294
+ find_field(:#{type}, name)
288
295
  end
289
296
 
290
- def #{kind}_type_name
297
+ def #{type}_type_name
291
298
  source = (respond_to?(:config) ? config : GraphQL.config)
292
- source.schema_type_names[:#{kind}]
299
+ source.schema_type_names[:#{type}]
293
300
  end
294
301
 
295
- def #{kind}_type
296
- if defined?(@#{kind}_fields) && @#{kind}_fields.present?
297
- OpenStruct.new(
298
- name: "\#{name}[:#{kind}]",
299
- kind: :object,
300
- object?: true,
301
- kind_enum: 'OBJECT',
302
- fields: @#{kind}_fields,
303
- gql_name: #{kind}_type_name,
304
- interfaces: nil,
305
- description: nil,
306
- interfaces?: false,
307
- internal?: false,
308
- ).freeze
309
- end
302
+ def #{type}_type
303
+ return unless #{type}_fields?
304
+
305
+ OpenStruct.new(
306
+ name: "\#{name}[:#{type}]",
307
+ kind: :object,
308
+ object?: true,
309
+ kind_enum: 'OBJECT',
310
+ fields: @#{type}_fields,
311
+ gql_name: #{type}_type_name,
312
+ description: nil,
313
+ output_type?: true,
314
+ operational?: true,
315
+ interfaces?: false,
316
+ internal?: false,
317
+ ).freeze
310
318
  end
311
319
  RUBY
312
320
  end
321
+
322
+ protected
323
+
324
+ # A little helper to define arguments using the :arguments key
325
+ def argument(*args, **xargs, &block)
326
+ xargs[:owner] = self
327
+ GraphQL::Argument.new(*args, **xargs, &block)
328
+ end
329
+
330
+ alias arg argument
331
+
332
+ private
333
+
334
+ def lazy_each_field_from(type)
335
+ fields_for(type).each_pair.lazy.each_entry.map(&:last) if fields_for?(type)
336
+ end
313
337
  end
314
338
  end
315
339
  end
@@ -26,7 +26,7 @@ module Rails
26
26
 
27
27
  protected
28
28
 
29
- # Enaqble introspection fields
29
+ # Enable introspection fields
30
30
  def enable_introspection!
31
31
  redefine_singleton_method(:introspection?) { true }
32
32
  introspection_dependencies!
@@ -123,8 +123,9 @@ module Rails
123
123
  initializer 'graphql.reloader', before: :load_config_initializers do |app|
124
124
  next unless (path = app.root.join('app', 'graphql')).exist?
125
125
 
126
- children = config.graphql.paths.join(',')
127
- autoloader = app.autoloaders.main
126
+ children = config.graphql.paths.to_a.join(',')
127
+ autoloader = app.respond_to?(:autoloaders) ? app.autoloaders : Rails.autoloaders
128
+ autoloader = autoloader.main
128
129
 
129
130
  ActiveSupport::Dependencies.autoload_paths.delete(path.to_s)
130
131
  autoloader.collapse(path.glob("**/{#{children}}").select(&:directory?))
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ base = ActionCable::Channel::Base
5
+ base = ApplicationCable::Channel if defined?(ApplicationCable::Channel)
6
+
7
+ BaseChannel = Class.new(base) do
8
+ include ::Rails::GraphQL::Channel
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ base = ActionController::Base
5
+ base = ApplicationController if defined?(ApplicationController)
6
+
7
+ BaseController = Class.new(base) do
8
+ include ::Rails::GraphQL::Controller
9
+
10
+ skip_before_action :verify_authenticity_token
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+ var queue = [];
2
+ var current = null;
3
+ var identifier = JSON.stringify({ channel: '<%= channel %>' });
4
+ var socket = new WebSocket("ws://" + window.location.hostname + "/<%= url %>");
5
+
6
+ // TOOD: This is a temporary implementation
7
+ socket.onopen = function(event) {
8
+ const msg = { command: 'subscribe', identifier: identifier };
9
+ socket.send(JSON.stringify(msg));
10
+ };
11
+
12
+ socket.onmessage = function(event) {
13
+ const msg = JSON.parse(event.data);
14
+ if (msg.type === "ping") {
15
+ return;
16
+ }
17
+
18
+ if (msg.type === "confirm_subscription") {
19
+ execute_next();
20
+ return;
21
+ }
22
+
23
+ if (msg.message && current) {
24
+ current.resolve(msg.message.result);
25
+ current = null;
26
+ execute_next();
27
+ } else {
28
+ console.dir(msg);
29
+ }
30
+ };
31
+
32
+ function execute_next() {
33
+ if (socket.readyState != '1' || queue.length === 0 || current) {
34
+ return;
35
+ }
36
+
37
+ current = queue.shift();
38
+ socket.send(JSON.stringify({
39
+ command: 'message',
40
+ identifier: identifier,
41
+ data: JSON.stringify({ action: 'execute', ...current.data }),
42
+ }));
43
+ }
44
+
45
+ function graphQLFetcher(graphQLParams) {
46
+ var resolve;
47
+ var promise = new Promise((success) => {
48
+ resolve = success;
49
+ });
50
+
51
+ var item = { data: graphQLParams, promise, resolve };
52
+
53
+ queue.push(item);
54
+ execute_next();
55
+ return promise;
56
+ };
@@ -0,0 +1,20 @@
1
+ function graphQLFetcher(graphQLParams) {
2
+ // This example expects a GraphQL server at the path /graphql.
3
+ // Change this to point wherever you host your GraphQL server.
4
+ return fetch('<%= url %>', {
5
+ method: 'post',
6
+ headers: {
7
+ 'Accept': 'application/json',
8
+ 'Content-Type': 'application/json'
9
+ },
10
+ body: JSON.stringify(graphQLParams),
11
+ }).then(function (response) {
12
+ return response.text();
13
+ }).then(function (responseBody) {
14
+ try {
15
+ return JSON.parse(responseBody);
16
+ } catch (error) {
17
+ return responseBody;
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,101 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="robots" content="noindex" />
6
+ <meta name="referrer" content="origin" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+ <title>SWAPI GraphQL API</title>
9
+ <style>
10
+ body {
11
+ height: 100vh;
12
+ margin: 0;
13
+ overflow: hidden;
14
+ }
15
+ #splash {
16
+ color: #333;
17
+ display: flex;
18
+ flex-direction: column;
19
+ font-family: system, -apple-system, "San Francisco", ".SFNSDisplay-Regular", "Segoe UI", Segoe, "Segoe WP", "Helvetica Neue", helvetica, "Lucida Grande", arial, sans-serif;
20
+ height: 100vh;
21
+ justify-content: center;
22
+ text-align: center;
23
+ }
24
+ </style>
25
+ <link rel="icon" href="favicon.ico">
26
+ <link type="text/css" href="//unpkg.com/graphiql/graphiql.min.css" rel="stylesheet" />
27
+ </head>
28
+ <body>
29
+ <div id="splash">
30
+ Loading&hellip;
31
+ </div>
32
+ <script src="//cdn.jsdelivr.net/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
33
+ <script src="https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js"></script>
34
+ <script src="https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js"></script>
35
+ <script src="//unpkg.com/graphiql/graphiql.min.js"></script>
36
+ <script>
37
+ // Parse the search string to get url parameters.
38
+ var search = window.location.search;
39
+ var parameters = {};
40
+ search.substr(1).split('&').forEach(function (entry) {
41
+ var eq = entry.indexOf('=');
42
+ if (eq >= 0) {
43
+ parameters[decodeURIComponent(entry.slice(0, eq))] =
44
+ decodeURIComponent(entry.slice(eq + 1));
45
+ }
46
+ });
47
+
48
+ // if variables was provided, try to format it.
49
+ if (parameters.variables) {
50
+ try {
51
+ parameters.variables =
52
+ JSON.stringify(JSON.parse(parameters.variables), null, 2);
53
+ } catch (e) {
54
+ // Do nothing, we want to display the invalid JSON as a string, rather
55
+ // than present an error.
56
+ }
57
+ }
58
+
59
+ // When the query and variables string is edited, update the URL bar so
60
+ // that it can be easily shared
61
+ function onEditQuery(newQuery) {
62
+ parameters.query = newQuery;
63
+ updateURL();
64
+ }
65
+ function onEditVariables(newVariables) {
66
+ parameters.variables = newVariables;
67
+ updateURL();
68
+ }
69
+ function onEditOperationName(newOperationName) {
70
+ parameters.operationName = newOperationName;
71
+ updateURL();
72
+ }
73
+ function updateURL() {
74
+ var newSearch = '?' + Object.keys(parameters).filter(function (key) {
75
+ return Boolean(parameters[key]);
76
+ }).map(function (key) {
77
+ return encodeURIComponent(key) + '=' +
78
+ encodeURIComponent(parameters[key]);
79
+ }).join('&');
80
+ history.replaceState(null, null, newSearch);
81
+ }
82
+
83
+ <%= render partial: "/#{settings[:mode]}", formats: :js, locals: settings %>
84
+
85
+ // Render <GraphiQL /> into the body.
86
+ ReactDOM.render(
87
+ React.createElement(GraphiQL, {
88
+ fetcher: graphQLFetcher,
89
+ query: parameters.query,
90
+ variables: parameters.variables,
91
+ operationName: parameters.operationName,
92
+ onEditQuery: onEditQuery,
93
+ onEditVariables: onEditVariables,
94
+ onEditOperationName: onEditOperationName
95
+ }),
96
+ document.body,
97
+ );
98
+ </script>
99
+ </body>
100
+ </html>
101
+
@@ -7,23 +7,17 @@ module Rails
7
7
  # A module to help generators to operate
8
8
  module BaseGenerator
9
9
  TEMPALTES_PATH = '../../../generators/graphql/templates'
10
+ APP_MODULE_NAME = Rails.application.class.name.chomp('::Application')
10
11
 
11
12
  def self.included(base)
13
+ base.const_set(:APP_MODULE_NAME, APP_MODULE_NAME)
12
14
  base.send(:namespace, "graphql:#{base.name.demodulize.underscore[0..-11]}")
13
15
  base.send(:source_root, File.expand_path(TEMPALTES_PATH, __dir__))
14
- base.send(:class_option, :directory,
15
- type: :string,
16
+ base.send(:class_option, :directory, type: :string,
16
17
  default: 'app/graphql',
17
18
  desc: 'Directory where generated files should be saved',
18
19
  )
19
20
  end
20
-
21
- protected
22
-
23
- def app_module_name
24
- require File.expand_path('config/application', destination_root)
25
- Rails.application.class.name.chomp('::Application')
26
- end
27
21
  end
28
22
  end
29
23
  end
@@ -79,12 +79,6 @@ module Rails
79
79
  }
80
80
  end
81
81
 
82
- # The list of ids of subscription and to which field they are
83
- # associated with
84
- def gql_subscriptions
85
- @gql_subscriptions ||= {}
86
- end
87
-
88
82
  # The instance of a GraphQL request. It can't simply perform using
89
83
  # +execute+, because it is important to check if any subscription was
90
84
  # generated
@@ -117,8 +111,8 @@ module Rails
117
111
  end
118
112
 
119
113
  # Get the GraphQL variables for a request
120
- def gql_variables(data)
121
- variables = data['variables']
114
+ def gql_variables(data, variables = nil)
115
+ variables ||= data['variables']
122
116
 
123
117
  case variables
124
118
  when ::ActionController::Parameters then variables.permit!.to_h
@@ -128,6 +122,12 @@ module Rails
128
122
  end
129
123
  end
130
124
 
125
+ # The list of ids of subscription and to which field they are
126
+ # associated with
127
+ def gql_subscriptions
128
+ @gql_subscriptions ||= {}
129
+ end
130
+
131
131
  # Remove all subscriptions
132
132
  def gql_clear_subscriptions
133
133
  gql_remove_subscription(*gql_subscriptions.keys) unless gql_subscriptions.empty?
@@ -22,6 +22,9 @@ module Rails
22
22
  # Each controller is assigned to a GraphQL schema on which the requests
23
23
  # will be performed from. It can be a string or the class
24
24
  class_attribute :gql_schema, instance_accessor: false
25
+
26
+ # Add the internal views directory
27
+ prepend_view_path("#{__dir__}/app/views")
25
28
  end
26
29
 
27
30
  # POST /execute
@@ -30,43 +33,43 @@ module Rails
30
33
  end
31
34
 
32
35
  # GET /describe
33
- def describe
34
- render plain: gql_schema_header + gql_describe_schema + gql_schema_footer
36
+ def describe(schema = gql_schema)
37
+ render plain: [
38
+ gql_schema_header(schema),
39
+ gql_describe_schema(schema),
40
+ gql_schema_footer,
41
+ ].join
42
+ end
43
+
44
+ # GET /graphiql
45
+ def graphiql
46
+ render '/graphiql', layout: false, locals: { settings: graphiql_settings }
35
47
  end
36
48
 
37
49
  protected
38
50
 
51
+ # Identifies if the request should be threated as a compiled request
52
+ def gql_compiled_request?(*)
53
+ false
54
+ end
55
+
39
56
  # Render a response as a GraphQL request
40
57
  def gql_request_response(*args, **xargs)
41
58
  render json: gql_request(*args, **xargs)
42
59
  end
43
60
 
44
- # Shows a text representation of the schema
45
- def gql_describe_schema(schema = gql_schema)
46
- schema.to_gql(
47
- with_descriptions: !params.key?(:without_descriptions),
48
- with_spec: !params.key?(:without_spec),
49
- )
50
- end
51
-
52
61
  # Execute a GraphQL request
53
- def gql_request(query, **xargs)
62
+ def gql_request(document, **xargs)
54
63
  request_xargs = REQUEST_XARGS.each_with_object({}) do |setting, result|
55
64
  result[setting] ||= (xargs[setting] || send(:"gql_#{setting}"))
56
65
  end
57
66
 
58
67
  request_xargs[:hash] ||= gql_query_cache_key
59
68
  request_xargs[:origin] ||= self
69
+ request_xargs[:compiled] ||= gql_compiled_request?(document)
60
70
 
61
71
  request_xargs = request_xargs.except(*%i[query_cache_key query_cache_version])
62
- ::Rails::GraphQL::Request.execute(query, **request_xargs)
63
- end
64
-
65
- # Print a header of the current schema for the description process
66
- # TODO: Maybe add a way to detect from which file the schema is being loaded
67
- def gql_schema_header
68
- ns = +" [#{gql_schema.namespace}]" if gql_schema.namespace != :base
69
- +"#{DESCRIBE_HEADER}# Schema #{gql_schema.name}#{ns}\n"
72
+ ::Rails::GraphQL::Request.execute(document, **request_xargs)
70
73
  end
71
74
 
72
75
  # The schema on which the requests will be performed from
@@ -87,10 +90,12 @@ module Rails
87
90
  end
88
91
 
89
92
  # Get the GraphQL query to execute
90
- def gql_query
93
+ def gql_document
91
94
  params[:query]
92
95
  end
93
96
 
97
+ alias gql_query gql_document
98
+
94
99
  # Get the cache key of the query for persisted queries
95
100
  def gql_query_cache_key(key = nil, version = nil)
96
101
  return unless (key ||= params[:query_cache_key]).present?
@@ -117,6 +122,30 @@ module Rails
117
122
  end
118
123
  end
119
124
 
125
+ # Return the settings for the GraphiQL view
126
+ def graphiql_settings(mode = nil)
127
+ if mode == :cable
128
+ { mode: :cable, url: '/cable', channel: 'GraphQL::BaseChannel' }
129
+ else
130
+ { mode: :fetch, url: '/graphql' }
131
+ end
132
+ end
133
+
134
+ # Shows a text representation of the schema
135
+ def gql_describe_schema(schema)
136
+ schema.to_gql(
137
+ with_descriptions: !params.key?(:without_descriptions),
138
+ with_spec: !params.key?(:without_spec),
139
+ )
140
+ end
141
+
142
+ # Print a header of the current schema for the description process
143
+ # TODO: Maybe add a way to detect from which file the schema is being loaded
144
+ def gql_schema_header(schema)
145
+ ns = +" [#{schema.namespace}]" if schema.namespace != :base
146
+ +"#{DESCRIBE_HEADER}# Schema #{schema.name}#{ns}\n"
147
+ end
148
+
120
149
  # Show the footer of the describe page
121
150
  def gql_schema_footer
122
151
  $/ + $/ + '# Version: ' + gql_version + $/ +
@@ -133,12 +162,8 @@ module Rails
133
162
 
134
163
  # Find the default application schema
135
164
  def gql_application_default_schema
136
- app_class = Rails.application.class
137
- source_name = app_class.respond_to?(:module_parent_name) \
138
- ? :module_parent_name \
139
- : :parent_name
140
-
141
- klass = "::GraphQL::#{app_class.public_send(source_name)}Schema".constantize
165
+ app_class = Rails.application.class.name.chomp('::Application')
166
+ klass = "::GraphQL::#{app_class}Schema".safe_constantize
142
167
  self.class.gql_schema = klass
143
168
  end
144
169
  end
@@ -35,7 +35,8 @@ module Rails
35
35
  end
36
36
  end
37
37
 
38
- delegate :key?, to: :@table
38
+ delegate :key?, :[], to: :@table
39
+ alias to_hash to_h
39
40
 
40
41
  class << self
41
42
  # Easy access to the easy loader method