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
@@ -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