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.
- checksums.yaml +4 -4
- data/ext/gql_parser.c +1 -16
- data/ext/gql_parser.h +21 -0
- data/ext/shared.c +0 -5
- data/ext/shared.h +6 -6
- data/lib/generators/graphql/channel_generator.rb +27 -0
- data/lib/generators/graphql/controller_generator.rb +9 -4
- data/lib/generators/graphql/install_generator.rb +49 -0
- data/lib/generators/graphql/schema_generator.rb +9 -4
- data/lib/generators/graphql/templates/channel.erb +7 -0
- data/lib/generators/graphql/templates/config.rb +97 -0
- data/lib/generators/graphql/templates/controller.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +5 -3
- data/lib/gql_parser.so +0 -0
- data/lib/rails/graphql/alternative/field_set.rb +12 -0
- data/lib/rails/graphql/alternative/query.rb +13 -8
- data/lib/rails/graphql/alternative/subscription.rb +2 -1
- data/lib/rails/graphql/alternative.rb +4 -0
- data/lib/rails/graphql/argument.rb +5 -3
- data/lib/rails/graphql/callback.rb +10 -8
- data/lib/rails/graphql/collectors/hash_collector.rb +12 -1
- data/lib/rails/graphql/collectors/json_collector.rb +21 -0
- data/lib/rails/graphql/config.rb +86 -59
- data/lib/rails/graphql/directive/include_directive.rb +0 -1
- data/lib/rails/graphql/directive/skip_directive.rb +0 -1
- data/lib/rails/graphql/directive/specified_by_directive.rb +24 -0
- data/lib/rails/graphql/directive.rb +31 -25
- data/lib/rails/graphql/event.rb +7 -6
- data/lib/rails/graphql/field/authorized_field.rb +0 -5
- data/lib/rails/graphql/field/input_field.rb +0 -5
- data/lib/rails/graphql/field/mutation_field.rb +5 -6
- data/lib/rails/graphql/field/output_field.rb +13 -2
- data/lib/rails/graphql/field/proxied_field.rb +6 -6
- data/lib/rails/graphql/field/resolved_field.rb +1 -1
- data/lib/rails/graphql/field/subscription_field.rb +35 -52
- data/lib/rails/graphql/field/typed_field.rb +26 -2
- data/lib/rails/graphql/field.rb +20 -19
- data/lib/rails/graphql/global_id.rb +5 -1
- data/lib/rails/graphql/helpers/inherited_collection/array.rb +1 -0
- data/lib/rails/graphql/helpers/inherited_collection/base.rb +3 -1
- data/lib/rails/graphql/helpers/inherited_collection/hash.rb +2 -1
- data/lib/rails/graphql/helpers/registerable.rb +1 -1
- data/lib/rails/graphql/helpers/with_arguments.rb +3 -2
- data/lib/rails/graphql/helpers/with_assignment.rb +5 -5
- data/lib/rails/graphql/helpers/with_callbacks.rb +3 -3
- data/lib/rails/graphql/helpers/with_description.rb +10 -8
- data/lib/rails/graphql/helpers/with_directives.rb +5 -1
- data/lib/rails/graphql/helpers/with_events.rb +1 -0
- data/lib/rails/graphql/helpers/with_fields.rb +30 -24
- data/lib/rails/graphql/helpers/with_name.rb +3 -2
- data/lib/rails/graphql/helpers/with_schema_fields.rb +75 -51
- data/lib/rails/graphql/introspection.rb +1 -1
- data/lib/rails/graphql/railtie.rb +3 -2
- data/lib/rails/graphql/railties/app/base_channel.rb +10 -0
- data/lib/rails/graphql/railties/app/base_controller.rb +12 -0
- data/lib/rails/graphql/railties/app/views/_cable.js.erb +56 -0
- data/lib/rails/graphql/railties/app/views/_fetch.js.erb +20 -0
- data/lib/rails/graphql/railties/app/views/graphiql.html.erb +101 -0
- data/lib/rails/graphql/railties/base_generator.rb +3 -9
- data/lib/rails/graphql/railties/channel.rb +8 -8
- data/lib/rails/graphql/railties/controller.rb +51 -26
- data/lib/rails/graphql/request/arguments.rb +2 -1
- data/lib/rails/graphql/request/backtrace.rb +31 -10
- data/lib/rails/graphql/request/component/field.rb +15 -8
- data/lib/rails/graphql/request/component/fragment.rb +13 -7
- data/lib/rails/graphql/request/component/operation/subscription.rb +4 -6
- data/lib/rails/graphql/request/component/operation.rb +12 -5
- data/lib/rails/graphql/request/component/spread.rb +13 -4
- data/lib/rails/graphql/request/component/typename.rb +1 -1
- data/lib/rails/graphql/request/component.rb +2 -0
- data/lib/rails/graphql/request/context.rb +1 -1
- data/lib/rails/graphql/request/event.rb +6 -2
- data/lib/rails/graphql/request/helpers/directives.rb +1 -0
- data/lib/rails/graphql/request/helpers/selection_set.rb +10 -4
- data/lib/rails/graphql/request/helpers/value_writers.rb +8 -5
- data/lib/rails/graphql/request/prepared_data.rb +3 -1
- data/lib/rails/graphql/request/steps/organizable.rb +1 -1
- data/lib/rails/graphql/request/steps/preparable.rb +1 -1
- data/lib/rails/graphql/request/steps/resolvable.rb +1 -1
- data/lib/rails/graphql/request/strategy/sequenced_strategy.rb +3 -3
- data/lib/rails/graphql/request/strategy.rb +18 -4
- data/lib/rails/graphql/request/subscription.rb +18 -16
- data/lib/rails/graphql/request.rb +71 -41
- data/lib/rails/graphql/schema.rb +39 -86
- data/lib/rails/graphql/shortcuts.rb +11 -5
- data/lib/rails/graphql/source/active_record/builders.rb +22 -24
- data/lib/rails/graphql/source/active_record_source.rb +96 -34
- data/lib/rails/graphql/source/base.rb +13 -40
- data/lib/rails/graphql/source/builder.rb +14 -22
- data/lib/rails/graphql/source/scoped_arguments.rb +10 -4
- data/lib/rails/graphql/source.rb +31 -38
- data/lib/rails/graphql/subscription/provider/action_cable.rb +10 -9
- data/lib/rails/graphql/subscription/provider/base.rb +6 -5
- data/lib/rails/graphql/subscription/store/base.rb +5 -9
- data/lib/rails/graphql/subscription/store/memory.rb +18 -9
- data/lib/rails/graphql/type/creator.rb +198 -0
- data/lib/rails/graphql/type/enum.rb +17 -9
- data/lib/rails/graphql/type/input.rb +30 -7
- data/lib/rails/graphql/type/interface.rb +15 -4
- data/lib/rails/graphql/type/object/directive_object.rb +6 -5
- data/lib/rails/graphql/type/object/input_value_object.rb +3 -4
- data/lib/rails/graphql/type/object/type_object.rb +40 -13
- data/lib/rails/graphql/type/object.rb +11 -6
- data/lib/rails/graphql/type/scalar/binary_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/date_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/date_time_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/decimal_scalar.rb +2 -0
- data/lib/rails/graphql/type/scalar/json_scalar.rb +3 -1
- data/lib/rails/graphql/type/scalar/time_scalar.rb +3 -1
- data/lib/rails/graphql/type/scalar.rb +2 -2
- data/lib/rails/graphql/type/union.rb +7 -2
- data/lib/rails/graphql/type.rb +10 -2
- data/lib/rails/graphql/type_map.rb +20 -7
- data/lib/rails/graphql/uri.rb +5 -4
- data/lib/rails/graphql/version.rb +6 -2
- data/lib/rails/graphql.rb +11 -8
- data/test/assets/introspection-mem.txt +1 -1
- data/test/assets/introspection.gql +2 -0
- data/test/assets/mem.gql +74 -60
- data/test/assets/mysql.gql +69 -55
- data/test/assets/sqlite.gql +78 -64
- data/test/assets/translate.gql +50 -39
- data/test/config.rb +2 -1
- data/test/graphql/schema_test.rb +2 -31
- data/test/graphql/source_test.rb +1 -11
- data/test/graphql/type/interface_test.rb +8 -5
- data/test/graphql/type/object_test.rb +8 -2
- data/test/graphql/type_map_test.rb +13 -16
- data/test/integration/global_id_test.rb +4 -4
- data/test/integration/memory/star_wars_validation_test.rb +2 -2
- data/test/integration/mysql/star_wars_introspection_test.rb +1 -1
- data/test/integration/resolver_precedence_test.rb +1 -1
- data/test/integration/schemas/memory.rb +3 -4
- data/test/integration/sqlite/star_wars_global_id_test.rb +27 -21
- data/test/integration/sqlite/star_wars_introspection_test.rb +1 -1
- data/test/integration/translate_test.rb +26 -14
- 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 |
|
21
|
-
fields = instance_variable_defined?("@#{
|
22
|
-
fields = fields ? instance_variable_get("@#{
|
23
|
-
fields.each_value { |field| subclass.add_proxy_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
|
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
|
189
|
+
# Get the list of GraphQL names of all the fields defined
|
184
190
|
def field_names_for(type, enabled_only = true)
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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 |
|
257
|
-
next unless public_send("#{
|
258
|
-
fields_for(
|
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 |
|
274
|
+
TYPE_FIELD_CLASS.each_key do |type|
|
268
275
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
269
|
-
def #{
|
270
|
-
|
276
|
+
def #{type}_fields?
|
277
|
+
defined?(@#{type}_fields) && @#{type}_fields.present?
|
271
278
|
end
|
272
279
|
|
273
|
-
def #{
|
274
|
-
|
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_#{
|
278
|
-
add_field(:#{
|
285
|
+
def add_#{type}_field(*args, **xargs, &block)
|
286
|
+
add_field(:#{type}, *args, **xargs, &block)
|
279
287
|
end
|
280
288
|
|
281
|
-
def #{
|
282
|
-
|
289
|
+
def #{type}_field?(name)
|
290
|
+
has_field?(:#{type}, name)
|
283
291
|
end
|
284
292
|
|
285
|
-
def #{
|
286
|
-
|
287
|
-
@#{kind}_fields if defined?(@#{kind}_fields)
|
293
|
+
def #{type}_field(name)
|
294
|
+
find_field(:#{type}, name)
|
288
295
|
end
|
289
296
|
|
290
|
-
def #{
|
297
|
+
def #{type}_type_name
|
291
298
|
source = (respond_to?(:config) ? config : GraphQL.config)
|
292
|
-
source.schema_type_names[:#{
|
299
|
+
source.schema_type_names[:#{type}]
|
293
300
|
end
|
294
301
|
|
295
|
-
def #{
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
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
|
@@ -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.
|
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,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…
|
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
|
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:
|
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(
|
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(
|
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
|
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
|
-
|
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
|