rails-graphql 1.0.0.beta → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -70,8 +70,11 @@ module Rails
70
70
  alias channel origin
71
71
 
72
72
  delegate :action_name, to: :controller, allow_nil: true
73
+ delegate :find_type!, to: :strategy, allow_nil: true
73
74
  delegate :all_listeners, :all_events, to: :schema
74
75
 
76
+ alias find_type find_type!
77
+
75
78
  class << self
76
79
  # Shortcut for initialize, set context, and execute
77
80
  def execute(*args, schema: nil, namespace: :base, context: {}, **xargs)
@@ -92,12 +95,12 @@ module Rails
92
95
 
93
96
  # Allow accessing component-based objects through the request
94
97
  def const_defined?(name, *)
95
- Component.const_defined?(name) || super
98
+ Component.const_defined?(name, false) || super
96
99
  end
97
100
 
98
101
  # Allow accessing component-based objects through the request
99
102
  def const_missing(name)
100
- Component.const_defined?(name) ? Component.const_get(name) : super
103
+ Component.const_defined?(name, false) ? Component.const_get(name, false) : super
101
104
  end
102
105
  end
103
106
 
@@ -105,8 +108,6 @@ module Rails
105
108
  def initialize(schema = nil, namespace: :base)
106
109
  @namespace = schema&.namespace || namespace
107
110
  @schema = GraphQL::Schema.find!(@namespace)
108
- @prepared_data = {}
109
- @extensions = {}
110
111
 
111
112
  ensure_schema!
112
113
  end
@@ -126,6 +127,11 @@ module Rails
126
127
  @context = build_ostruct(data).freeze
127
128
  end
128
129
 
130
+ # Allow adding extra information to the response, in a extensions key
131
+ def extensions
132
+ @extensions ||= {}
133
+ end
134
+
129
135
  # Execute a given document with the given arguments
130
136
  def execute(document, **xargs)
131
137
  output = xargs.delete(:as) || schema.config.default_response_format
@@ -134,14 +140,15 @@ module Rails
134
140
 
135
141
  document, cache = nil, document if xargs.delete(:compiled)
136
142
  prepared_data = xargs.delete(:data_for)
137
-
138
143
  reset!(**xargs)
139
- prepared_data&.each { |key, value| prepare_data_for(key, value) }
144
+
140
145
  @response = initialize_response(output, formatter)
146
+ import_prepared_data(prepared_data)
141
147
  execute!(document, cache)
142
148
 
143
149
  response.public_send(formatter)
144
150
  rescue StaticResponse
151
+ # TODO: Maybe change this to a throw/catch instead
145
152
  response.public_send(formatter)
146
153
  end
147
154
 
@@ -178,31 +185,43 @@ module Rails
178
185
  # This is used by cache and static responses to jump from executing to
179
186
  # delivery a response right away
180
187
  def force_response(response, error = StaticResponse)
188
+ return unless defined?(@response)
181
189
  @response = response
182
190
  raise error
183
191
  end
184
192
 
193
+ # Import prepared data that is formatted as a hash
194
+ def import_prepared_data(prepared_data)
195
+ prepared_data&.each do |key, value|
196
+ prepare_data_for(key, value)
197
+ end
198
+ end
199
+
185
200
  # Add a new prepared data from +value+ to the given +field+
186
201
  def prepare_data_for(field, value, **options)
187
202
  field = PreparedData.lookup(self, field)
188
203
 
189
- if @prepared_data.key?(field)
190
- @prepared_data[field].push(value)
204
+ if prepared_data.key?(field)
205
+ prepared_data[field].push(value)
191
206
  else
192
- @prepared_data[field] = PreparedData.new(field, value, **options)
207
+ prepared_data[field] = PreparedData.new(field, value, **options)
193
208
  end
194
209
  end
195
210
 
196
211
  # Recover the next prepared data for the given field
197
212
  def prepared_data_for(field)
213
+ return unless defined?(@prepared_data)
214
+
198
215
  field = field.field if field.is_a?(Component::Field)
199
- @prepared_data[field]
216
+ prepared_data[field]
200
217
  end
201
218
 
202
219
  # Check if the given field has prepared data
203
220
  def prepared_data_for?(field)
221
+ return false unless defined?(@prepared_data)
222
+
204
223
  field = field.field if field.is_a?(Component::Field)
205
- defined?(@prepared_data) && @prepared_data.key?(field)
224
+ prepared_data.key?(field)
206
225
  end
207
226
 
208
227
  # Build a easy-to-access object representing the current information of
@@ -231,7 +250,7 @@ module Rails
231
250
 
232
251
  # A little helper to report an error on a given node
233
252
  def report_node_error(message, node, **xargs)
234
- xargs[:locations] ||= location_of(node) unless xargs.key?(:line)
253
+ xargs[:locations] ||= location_of(node)
235
254
  report_error(message, **xargs)
236
255
  end
237
256
 
@@ -251,8 +270,7 @@ module Rails
251
270
  xargs[:path] ||= stack_to_path
252
271
  errors.add(message, **xargs)
253
272
 
254
- # Return nil for easier usage
255
- nil
273
+ nil # Return nil for easier usage
256
274
  end
257
275
 
258
276
  # Add the given +object+ into the execution +stack+ and execute the given
@@ -271,25 +289,25 @@ module Rails
271
289
  end.compact.reverse
272
290
  end
273
291
 
274
- # Add extensions to the request, which ensures a bunch of extended
275
- # behaviors for all the objects created through the request
292
+ # Add class extensions to the request, which ensures a bunch of
293
+ # extended behaviors for all the objects created through the request
276
294
  def extend(*modules)
277
- import_extensions(*modules)
278
- request_ext = extensions[self.class]
295
+ import_class_extensions(*modules)
296
+ request_ext = class_extensions[self.class]
279
297
  super(request_ext) if request_ext && !is_a?(request_ext)
280
298
  end
281
299
 
282
- # This initiates a new object which is aware of extensions
300
+ # This initiates a new object which is aware of class extensions
283
301
  def build(klass, *args, &block)
284
- ext_module = extensions[klass]
302
+ ext_module = class_extensions[klass]
285
303
  obj = klass.new(*args, &block)
286
304
  obj.extend(ext_module) if ext_module
287
305
  obj
288
306
  end
289
307
 
290
- # This allocates a new object which is aware of extensions
308
+ # This allocates a new object which is aware of class extensions
291
309
  def build_from_cache(klass)
292
- ext_module = extensions[klass]
310
+ ext_module = class_extensions[klass]
293
311
  obj = klass.allocate
294
312
  obj.extend(ext_module) if ext_module
295
313
  obj
@@ -349,6 +367,7 @@ module Rails
349
367
  resolve_from_cache = (version == schema.version)
350
368
 
351
369
  # Run the document from scratch if TypeMap has changed
370
+ # TODO: We need to save the new organized document
352
371
  return run_document unless resolve_from_cache
353
372
  @valid_cache = true unless defined?(@valid_cache)
354
373
 
@@ -360,9 +379,19 @@ module Rails
360
379
  @strategy.resolve!
361
380
  end
362
381
 
363
- private
382
+ protected
364
383
 
365
- attr_reader :extensions
384
+ # Stores all the class extensions
385
+ def class_extensions
386
+ @class_extensions ||= {}
387
+ end
388
+
389
+ # Stores all the prepared data, but only when it is needed
390
+ def prepared_data
391
+ @prepared_data ||= {}
392
+ end
393
+
394
+ private
366
395
 
367
396
  # Reset principal variables and set the given +args+
368
397
  def reset!(args: nil, variables: {}, operation_name: nil, origin: nil)
@@ -382,8 +411,6 @@ module Rails
382
411
  @stack = [schema]
383
412
  @cache = {}
384
413
  @log_extra = {}
385
- @fragments = {}
386
- @operations = {}
387
414
  @subscriptions = {}
388
415
  @used_variables = Set.new
389
416
 
@@ -401,14 +428,18 @@ module Rails
401
428
  ensure
402
429
  report_unused_variables
403
430
  write_cache_request(cache) if cache.present? && !valid_cache?
431
+ @response.try(:append_errors, errors)
432
+
433
+ if defined?(@extensions)
434
+ @response.try(:append_extensions, @extensions)
435
+ @extensions.clear
436
+ end
404
437
 
405
438
  @cache.clear
406
439
  @strategy&.clear
407
440
  @fragments&.clear
408
- @operations.clear
409
- @prepared_data.clear
410
-
411
- @response.try(:append_errors, errors)
441
+ @operations&.clear
442
+ @prepared_data&.clear
412
443
  end
413
444
 
414
445
  # Prepare the definitions, find the strategy and resolve
@@ -416,9 +447,8 @@ module Rails
416
447
  return if @document.nil?
417
448
 
418
449
  collect_definitions!
419
-
420
450
  @strategy ||= find_strategy!
421
- @strategy.trigger_event(:request)
451
+ @strategy.trigger_event(:request) if with == :resolve!
422
452
  @strategy.public_send(with)
423
453
  end
424
454
 
@@ -440,26 +470,26 @@ module Rails
440
470
  build(klass, self)
441
471
  end
442
472
 
443
- # Find all necessary extensions inside the given +modules+ and prepare
444
- # the extension base module
445
- def import_extensions(*modules)
473
+ # Find all necessary class extensions inside the given +modules+
474
+ # and prepare the extension base module
475
+ def import_class_extensions(*modules)
446
476
  modules.each do |mod|
447
477
  mod.constants.each do |const_name|
448
478
  const_name = const_name.to_s
449
- const = mod.const_get(const_name)
479
+ const = mod.const_get(const_name, false)
450
480
  next unless const.is_a?(Module)
451
481
 
452
482
  # Find the related request class to extend
453
483
  klass = const_name === 'Request' ? self.class : begin
454
484
  const_name.split('_').inject(self.class) do |k, next_const|
455
- k.const_defined?(next_const) ? k.const_get(next_const) : break
485
+ k.const_defined?(next_const) ? k.const_get(next_const, false) : break
456
486
  end
457
487
  end
458
488
 
459
- # Create the shared module and include the extension
489
+ # Create the shared module and include the class extension
460
490
  next unless klass&.is_a?(Class)
461
- extensions[klass] ||= Module.new
462
- extensions[klass].include(const)
491
+ class_extensions[klass] ||= Module.new
492
+ class_extensions[klass].include(const)
463
493
  end
464
494
  end
465
495
  end
@@ -477,7 +507,7 @@ module Rails
477
507
  # Build the payload to be sent to the log
478
508
  def log_payload(data)
479
509
  name = @operation_name.presence
480
- name ||= operations.keys.first if operations.size.eql?(1)
510
+ name ||= operations.keys.first if operations&.size&.eql?(1)
481
511
  map_variables = args.to_h.transform_keys do |key|
482
512
  @arg_names[key.to_s]
483
513
  end
@@ -57,7 +57,7 @@ module Rails
57
57
  # :singleton-method:
58
58
  # Since there are only one schema per namespace, the name is constant
59
59
  def gql_name
60
- 'schema'
60
+ '__Schema'
61
61
  end
62
62
 
63
63
  alias graphql_name gql_name
@@ -136,21 +136,6 @@ module Rails
136
136
  namespace
137
137
  end
138
138
 
139
- # Return the subscription provider for the current schema
140
- def subscription_provider
141
- if !defined?(@subscription_provider)
142
- @subscription_provider = config.default_subscription_provider
143
- subscription_provider
144
- elsif @subscription_provider.is_a?(String)
145
- provider = (name = @subscription_provider).safe_constantize
146
- return @subscription_provider = provider.new(logger: logger) unless provider.nil?
147
-
148
- raise ::NameError, +"uninitialized constant #{name}"
149
- else
150
- @subscription_provider
151
- end
152
- end
153
-
154
139
  # Check if the schema is valid
155
140
  def valid?
156
141
  defined?(@validated) && @validated
@@ -221,6 +206,35 @@ module Rails
221
206
  type_map.fetch!(directive, **xargs)
222
207
  end
223
208
 
209
+ # See {Request}[rdoc-ref:Rails::GraphQL::Request]
210
+ def request
211
+ return if self == ::Rails::GraphQL::Schema
212
+ Rails::GraphQL::Request.new(self)
213
+ end
214
+
215
+ # See {Request}[rdoc-ref:Rails::GraphQL::Request]
216
+ def execute(*args, **xargs)
217
+ return if self == ::Rails::GraphQL::Schema
218
+ Rails::GraphQL::Request.execute(*args, **xargs, schema: self)
219
+ end
220
+
221
+ alias perform execute
222
+
223
+ # Return the subscription provider for the current schema
224
+ def subscription_provider
225
+ if !defined?(@subscription_provider)
226
+ @subscription_provider = config.subscription_provider || config.default_subscription_provider
227
+ subscription_provider
228
+ elsif @subscription_provider.is_a?(String)
229
+ provider = (name = @subscription_provider).safe_constantize
230
+ return @subscription_provider = provider.new(logger: logger) unless provider.nil?
231
+
232
+ raise ::NameError, +"uninitialized constant #{name}"
233
+ else
234
+ @subscription_provider
235
+ end
236
+ end
237
+
224
238
  # Remove subscriptions by their provided +sids+
225
239
  def remove_subscriptions(*sids)
226
240
  subscription_provider&.remove(*sids)
@@ -286,12 +300,10 @@ module Rails
286
300
  end
287
301
 
288
302
  protected
289
-
290
303
  attr_writer :subscription_provider
291
304
 
292
305
  # Mark the given class to be pending of registration
293
306
  def inherited(subclass)
294
- subclass.spec_object = false
295
307
  subclass.abstract = false
296
308
  super if defined? super
297
309
 
@@ -301,6 +313,11 @@ module Rails
301
313
  end
302
314
  end
303
315
 
316
+ # Syntax sugar for beginners
317
+ def field(*args, **xargs, &block)
318
+ add_field(:query, *args, **xargs, &block)
319
+ end
320
+
304
321
  # Indicate to type map that the current schema depends on all the
305
322
  # files in the provided +path+ directory
306
323
  def load_directory(dir = '.', recursive: true)
@@ -376,20 +393,9 @@ module Rails
376
393
  superclass ||= GraphQL::Source.find_for!(object)
377
394
 
378
395
  xargs[:suffix] = 'Source'
379
- create_and_build = build
380
- schema_namespace = namespace
381
-
382
- create_klass(object, superclass, GraphQL::Source, **xargs) do
383
- set_namespace schema_namespace
384
-
385
- xargs.each do |key, value|
386
- _, segment = key.to_s.split('skip_on_')
387
- skip_on segment, value if segment.present?
388
- end
396
+ xargs[:build] = build
389
397
 
390
- instance_exec(&block) if block.present?
391
- build_all if create_and_build
392
- end
398
+ create_type(object, superclass, **xargs, &block)
393
399
  end
394
400
 
395
401
  # Helper method to create multiple sources with the same type
@@ -402,61 +408,8 @@ module Rails
402
408
 
403
409
  # A simpler way to create a new type object without having to create
404
410
  # a class in a different file
405
- def create_type(name, superclass, **xargs, &block)
406
- superclass = GraphQL::Type.const_get(superclass) unless superclass.is_a?(Module)
407
- xargs[:suffix] ||= superclass.base_type.name.demodulize
408
-
409
- create_klass(name, superclass, GraphQL::Type, **xargs, &block)
410
- end
411
-
412
- private
413
-
414
- # Helper to create objects that are actually classes of a given
415
- # +superclass+ ensuring that it inherits from +base_class+.
416
- #
417
- # The +suffix+ option can ensures that the name of the created
418
- # class ends with a specific suffix.
419
- def create_klass(name_or_object, superclass, base_class = nil, **xargs, &block)
420
- name = name_or_object.is_a?(Module) ? name_or_object.name : name_or_object.to_s
421
-
422
- base_module = name.classify.deconstantize
423
- base_module.prepend('GraphQL::') unless base_module =~ /^GraphQL(::|$)/
424
- base_module = base_module.delete_suffix('::').constantize
425
-
426
- klass_name = name.classify.demodulize
427
- klass_name += xargs[:suffix] if xargs.key?(:suffix) &&
428
- !klass_name.end_with?(xargs[:suffix])
429
-
430
- if base_module.const_defined?(klass_name)
431
- klass = base_module.const_get(klass_name)
432
-
433
- raise DuplicatedError, (+<<~MSG).squish unless !xargs[:once] && klass < superclass
434
- A constant named "#{klass_name}" already exists for the
435
- "#{base_module.name}" module.
436
- MSG
437
-
438
- # This likely happened because the classes are being reloaded, so
439
- # call inherited again as if the class has just been created
440
- superclass.inherited(klass)
441
- else
442
- base_class ||= superclass.ancestors.find { |k| k.superclass === Class }
443
-
444
- valid = superclass.is_a?(Module) && superclass < base_class
445
- raise DefinitionError, (+<<~MSG).squish unless valid
446
- The given "#{superclass}" superclass does not inherites from
447
- #{base_class.name} class.
448
- MSG
449
-
450
- klass = base_module.const_set(klass_name, Class.new(superclass))
451
- end
452
-
453
- klass.abstract = xargs[:abstract] if xargs.key?(:abstract)
454
- klass.assigned_to = name_or_object if name_or_object.is_a?(Module) &&
455
- klass.is_a?(Helpers::WithAssignment)
456
-
457
- klass.set_namespace(namespace)
458
- klass.module_exec(&block) if block.present?
459
- klass
411
+ def create_type(*args, **xargs, &block)
412
+ GraphQL::Type.create!(self, *args, **xargs, &block)
460
413
  end
461
414
  end
462
415
  end
@@ -2,23 +2,31 @@
2
2
 
3
3
  # This exposed module allows some shortcuts while working outside of the gem
4
4
  module GraphQL
5
+ autoload :BaseController, "#{__dir__}/railties/app/base_controller.rb"
6
+ autoload :BaseChannel, "#{__dir__}/railties/app/base_channel.rb"
7
+
5
8
  # List of constant shortcuts, as string to not trigger autoload
6
9
  CONST_SHORTCUTS = {
7
10
  CacheKey: '::Rails::GraphQL::CacheKey',
8
11
  Channel: '::Rails::GraphQL::Channel',
9
12
  Controller: '::Rails::GraphQL::Controller',
10
13
  Directive: '::Rails::GraphQL::Directive',
11
- Field: '::Rails::GraphQL::Field',
12
14
  GlobalID: '::Rails::GraphQL::GlobalID',
13
15
  Request: '::Rails::GraphQL::Request',
14
16
  Schema: '::Rails::GraphQL::Schema',
15
17
  Source: '::Rails::GraphQL::Source',
16
18
  Type: '::Rails::GraphQL::Type',
17
19
 
20
+ Field: '::Rails::GraphQL::Alternative::Field',
18
21
  Query: '::Rails::GraphQL::Alternative::Query',
19
22
  Mutation: '::Rails::GraphQL::Alternative::Mutation',
20
23
  Subscription: '::Rails::GraphQL::Alternative::Subscription',
21
24
 
25
+ FieldSet: '::Rails::GraphQL::Alternative::FieldSet',
26
+ QuerySet: '::Rails::GraphQL::Alternative::QuerySet',
27
+ MutationSet: '::Rails::GraphQL::Alternative::MutationSet',
28
+ SubscriptionSet: '::Rails::GraphQL::Alternative::SubscriptionSet',
29
+
22
30
  Enum: '::Rails::GraphQL::Type::Enum',
23
31
  Input: '::Rails::GraphQL::Type::Input',
24
32
  Interface: '::Rails::GraphQL::Type::Interface',
@@ -26,9 +34,6 @@ module GraphQL
26
34
  Scalar: '::Rails::GraphQL::Type::Scalar',
27
35
  Union: '::Rails::GraphQL::Type::Union',
28
36
 
29
- ProxyField: '::Rails::GraphQL::Field::ProxyField',
30
- AssociationField: '::Rails::GraphQL::Field::AssociationField',
31
-
32
37
  BaseSource: '::Rails::GraphQL::Source::BaseSource',
33
38
  ActiveRecordSource: '::Rails::GraphQL::Source::ActiveRecordSource',
34
39
  }.freeze
@@ -43,7 +48,8 @@ module GraphQL
43
48
  #
44
49
  # Rails::GraphQL::Directive::DeprecatedDirective(...)
45
50
  # # => Rails::GraphQL::Directive::DeprecatedDirective.new(...)
46
- DIRECTIVE_SHORTCUTS = %i[DeprecatedDirective IncludeDirective SkipDirective].freeze
51
+ DIRECTIVE_SHORTCUTS = %i[DeprecatedDirective IncludeDirective SkipDirective
52
+ SpecifiedByDirective].freeze
47
53
 
48
54
  class << self
49
55
  delegate *DIRECTIVE_SHORTCUTS, to: 'Rails::GraphQL::Directive'
@@ -4,11 +4,6 @@ module Rails
4
4
  module GraphQL
5
5
  # All the helper methods for building the source
6
6
  module Source::ActiveRecordSource::Builders
7
- # Override the object class to identify interfaces due to STI
8
- def object_class
9
- sti_interface? ? interface_class : super
10
- end
11
-
12
7
  # List of all columns that should be threated as IDs
13
8
  # TODO: Add a exclusive cache for the build process
14
9
  def id_columns
@@ -62,9 +57,7 @@ module Rails
62
57
  # Check if the given model is consider an interface due to single table
63
58
  # inheritance and the given model is the base class
64
59
  def sti_interface?
65
- @sti_interface ||= begin
66
- model.has_attribute?(model.inheritance_column) && model.base_class == model
67
- end
60
+ model.has_attribute?(model.inheritance_column) && model.base_class == model
68
61
  end
69
62
 
70
63
  # Build all enums associated to the class, collecting them from the
@@ -73,8 +66,8 @@ module Rails
73
66
  return remove_instance_variable(:@enums) if enums.blank?
74
67
 
75
68
  @enums = enums.each_with_object({}) do |(attribute, setting), hash|
76
- class_name = base_name + attribute.to_s.classify
77
- hash[attribute.to_s] = create_enum(class_name, setting, once: true)
69
+ class_name = base_name.tr('_', '') + attribute.to_s.classify
70
+ hash[attribute.to_s] = create_enum(class_name, setting)
78
71
  rescue DuplicatedError
79
72
  next
80
73
  end.freeze
@@ -82,15 +75,14 @@ module Rails
82
75
 
83
76
  # Build all necessary attribute fields into the given +holder+
84
77
  def build_attribute_fields(holder, **field_options)
85
- each_attribute(holder) do |key, type, **options|
86
- next if holder.field?(key) || skip_field?(key, on: holder.kind)
87
78
 
79
+ each_attribute(holder) do |key, type, **options|
88
80
  str_key = key.to_s
89
81
  type = (defined?(@enums) && @enums.key?(str_key) && @enums[str_key]) ||
90
82
  (id_columns.include?(str_key) && :id) || type
91
83
 
92
84
  options[:null] = !attr_required?(key) unless options.key?(:null)
93
- holder.field(key, type, **options.merge(field_options[key] || {}))
85
+ holder.safe_field(key, type, **options.merge(field_options[key] || {}))
94
86
  end
95
87
  end
96
88
 
@@ -99,20 +91,22 @@ module Rails
99
91
  return unless with_associations?
100
92
 
101
93
  each_reflection do |item|
102
- next if holder.field?(item.name) || item.polymorphic? ||
94
+ next if holder.has_field?(item.name) || item.polymorphic? ||
103
95
  skip_field?(item.name, on: holder.kind)
104
96
 
105
97
  type_map_after_register(item.klass) do |type|
106
- next unless (type.object? && type.try(:assigned_to) != item.klass) ||
107
- type.interface?
98
+ next unless type.try(:assigned_to) != item.klass ||
99
+ type.input_type? || type.leaf_type?
108
100
 
109
101
  options = reflection_to_options(item)
110
102
 
111
- if type <= Source::Base
112
- source_name = item.collection? ? type.plural : type.singular
113
- proxy_options = options.merge(alias: reflection.name, of_type: :proxy)
103
+ if item.collection?
104
+ owner = type.try(:owner)
105
+ source = owner.is_a?(Helpers::WithSchemaFields) &&
106
+ (owner.try(:collection_field) || owner.query_fields.try(:[], item.name))
114
107
 
115
- if (source = type.query_fields[source_name]).present?
108
+ if source.present?
109
+ proxy_options = options.merge(alias: item.name, of_type: :proxy)
116
110
  field = holder.safe_field(source, **proxy_options)
117
111
  end
118
112
  end
@@ -130,16 +124,20 @@ module Rails
130
124
  def build_reflection_inputs(holder)
131
125
  return unless with_associations?
132
126
 
127
+ suffix = GraphQL.config.auto_suffix_input_objects
133
128
  model.nested_attributes_options.each_key do |reflection_name|
134
- next if (reflection = model._reflect_on_association(reflection_name)).nil?
129
+ reflection = model._reflect_on_association(reflection_name)
130
+ next if reflection.nil? || reflection.polymorphic?
135
131
 
136
132
  expected_name = reflection.klass.name.tr(':', '')
137
- expected_name += 'Input' unless expected_name.end_with?('Input')
133
+ expected_name += suffix unless expected_name.end_with?(suffix)
134
+
135
+ type_map_after_register(expected_name) do |type|
136
+ next unless type.input?
138
137
 
139
- type_map_after_register(expected_name) do |input|
140
138
  options = reflection_to_options(reflection).merge(null: true)
141
139
  field_name = "#{reflection.name}_attributes"
142
- holder.safe_field(field_name, input, **options)
140
+ holder.safe_field(field_name, type, **options)
143
141
  end
144
142
  end
145
143
  end