Skip to content

Commit 62a2df0

Browse files
authored
Merge pull request #5055 from rmosolgo/lazy-root-types
Accept blocks to reference schema root types
2 parents 92273b8 + b3dc381 commit 62a2df0

File tree

10 files changed

+146
-25
lines changed

10 files changed

+146
-25
lines changed

lib/graphql/rubocop.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
require "graphql/rubocop/graphql/default_null_true"
44
require "graphql/rubocop/graphql/default_required_true"
55
require "graphql/rubocop/graphql/field_type_in_block"
6+
require "graphql/rubocop/graphql/root_types_in_block"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
require_relative "./base_cop"
3+
4+
module GraphQL
5+
module Rubocop
6+
module GraphQL
7+
# Identify (and auto-correct) any root types in your schema file.
8+
#
9+
# @example
10+
# # bad, immediately causes Rails to load `app/graphql/types/query.rb`
11+
# query Types::Query
12+
#
13+
# # good, defers loading until the file is needed
14+
# query { Types::Query }
15+
#
16+
class RootTypesInBlock < BaseCop
17+
MSG = "type configuration can be moved to a block to defer loading the type's file"
18+
19+
def_node_matcher :root_type_config_without_block, <<-Pattern
20+
(
21+
send nil? {:query :mutation :subscription} const
22+
)
23+
Pattern
24+
25+
def on_send(node)
26+
root_type_config_without_block(node) do
27+
add_offense(node) do |corrector|
28+
new_node_source = node.source_range.source
29+
new_node_source.sub!(/(query|mutation|subscription)/, '\1 {')
30+
new_node_source << " }"
31+
corrector.replace(node, new_node_source)
32+
end
33+
end
34+
end
35+
end
36+
end
37+
end
38+
end

lib/graphql/schema.rb

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -430,44 +430,60 @@ def connections
430430
end
431431
end
432432

433-
def query(new_query_object = nil)
434-
if new_query_object
433+
def query(new_query_object = nil, &lazy_load_block)
434+
if new_query_object || block_given?
435435
if @query_object
436-
raise GraphQL::Error, "Second definition of `query(...)` (#{new_query_object.inspect}) is invalid, already configured with #{@query_object.inspect}"
436+
dup_defn = new_query_object || yield
437+
raise GraphQL::Error, "Second definition of `query(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@query_object.inspect}"
438+
elsif use_schema_subset?
439+
@query_object = block_given? ? lazy_load_block : new_query_object
437440
else
438-
@query_object = new_query_object
439-
add_type_and_traverse(new_query_object, root: true) unless use_schema_subset?
440-
nil
441+
@query_object = new_query_object || lazy_load_block.call
442+
add_type_and_traverse(@query_object, root: true)
441443
end
444+
nil
445+
elsif @query_object.is_a?(Proc)
446+
@query_object = @query_object.call
442447
else
443448
@query_object || find_inherited_value(:query)
444449
end
445450
end
446451

447-
def mutation(new_mutation_object = nil)
448-
if new_mutation_object
452+
def mutation(new_mutation_object = nil, &lazy_load_block)
453+
if new_mutation_object || block_given?
449454
if @mutation_object
450-
raise GraphQL::Error, "Second definition of `mutation(...)` (#{new_mutation_object.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
455+
dup_defn = new_mutation_object || yield
456+
raise GraphQL::Error, "Second definition of `mutation(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@mutation_object.inspect}"
457+
elsif use_schema_subset?
458+
@mutation_object = block_given? ? lazy_load_block : new_mutation_object
451459
else
452-
@mutation_object = new_mutation_object
453-
add_type_and_traverse(new_mutation_object, root: true) unless use_schema_subset?
454-
nil
460+
@mutation_object = new_mutation_object || lazy_load_block.call
461+
add_type_and_traverse(@mutation_object, root: true)
455462
end
463+
nil
464+
elsif @mutation_object.is_a?(Proc)
465+
@mutation_object = @mutation_object.call
456466
else
457467
@mutation_object || find_inherited_value(:mutation)
458468
end
459469
end
460470

461-
def subscription(new_subscription_object = nil)
462-
if new_subscription_object
471+
def subscription(new_subscription_object = nil, &lazy_load_block)
472+
if new_subscription_object || block_given?
463473
if @subscription_object
464-
raise GraphQL::Error, "Second definition of `subscription(...)` (#{new_subscription_object.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
474+
dup_defn = new_subscription_object || yield
475+
raise GraphQL::Error, "Second definition of `subscription(...)` (#{dup_defn.inspect}) is invalid, already configured with #{@subscription_object.inspect}"
476+
elsif use_schema_subset?
477+
@subscription_object = block_given? ? lazy_load_block : new_subscription_object
465478
else
466-
@subscription_object = new_subscription_object
467-
add_subscription_extension_if_necessary
468-
add_type_and_traverse(new_subscription_object, root: true) unless use_schema_subset?
469-
nil
479+
@subscription_object = new_subscription_object || lazy_load_block.call
480+
add_type_and_traverse(@subscription_object, root: true)
470481
end
482+
nil
483+
elsif @subscription_object.is_a?(Proc)
484+
@subscription_object = @subscription_object.call
485+
add_subscription_extension_if_necessary
486+
@subscription_object
471487
else
472488
@subscription_object || find_inherited_value(:subscription)
473489
end
@@ -1373,7 +1389,8 @@ def instrumenters
13731389

13741390
# @api private
13751391
def add_subscription_extension_if_necessary
1376-
if !defined?(@subscription_extension_added) && subscription && self.subscriptions
1392+
# TODO: when there's a proper API for extending root types, migrat this to use it.
1393+
if !defined?(@subscription_extension_added) && @subscription_object.is_a?(Class) && self.subscriptions
13771394
@subscription_extension_added = true
13781395
subscription.all_field_definitions.each do |field|
13791396
if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }

spec/fixtures/cop/.rubocop.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ GraphQL/FieldTypeInBlock:
1616
Include:
1717
- field_type.rb
1818
- field_type_autocorrect.rb
19+
20+
GraphQL/RootTypesInBlock:
21+
Enabled: true
22+
Include:
23+
- root_types.rb
24+
- root_types_autocorrect.rb

spec/fixtures/cop/root_types.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class MyAppSchema < GraphQL::Schema
2+
query Types::Query
3+
mutation Types::Mutation
4+
subscription Types::Subscription
5+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class MyAppSchema < GraphQL::Schema
2+
query { Types::Query }
3+
mutation { Types::Mutation }
4+
subscription { Types::Subscription }
5+
end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
require 'spec_helper'
3+
4+
describe "GraphQL::Cop::RootTypesInBlock" do
5+
include RubocopTestHelpers
6+
7+
it "finds and autocorrects field corrections with inline types" do
8+
result = run_rubocop_on("spec/fixtures/cop/root_types.rb")
9+
puts result
10+
assert_equal 3, rubocop_errors(result)
11+
12+
assert_includes result, <<-RUBY
13+
query Types::Query
14+
^^^^^^^^^^^^^^^^^^
15+
RUBY
16+
17+
assert_includes result, <<-RUBY
18+
mutation Types::Mutation
19+
^^^^^^^^^^^^^^^^^^^^^^^^
20+
RUBY
21+
22+
assert_includes result, <<-RUBY
23+
subscription Types::Subscription
24+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25+
RUBY
26+
27+
assert_rubocop_autocorrects_all("spec/fixtures/cop/root_types.rb")
28+
end
29+
end

spec/graphql/schema_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,4 +476,24 @@ class QueryRequiredSchema < GraphQL::Schema
476476
it "starts with no references_to" do
477477
assert_equal({}, GraphQL::Schema.references_to)
478478
end
479+
480+
it "defers root type blocks until those types are used" do
481+
calls = []
482+
schema = Class.new(GraphQL::Schema) do
483+
self.use_schema_subset = true
484+
query { calls << :query; Class.new(GraphQL::Schema::Object) { graphql_name("Query") } }
485+
mutation { calls << :mutation; Class.new(GraphQL::Schema::Object) { graphql_name("Mutation") } }
486+
subscription { calls << :subscription; Class.new(GraphQL::Schema::Object) { graphql_name("Subscription") } }
487+
# Test this because it tries to modify `subscription` -- currently hardcoded in Schema.add_subscription_extension_if_necessary
488+
use GraphQL::Subscriptions
489+
end
490+
491+
assert_equal [], calls
492+
assert_equal "Query", schema.query.graphql_name
493+
assert_equal [:query], calls
494+
assert_equal "Mutation", schema.mutation.graphql_name
495+
assert_equal [:query, :mutation], calls
496+
assert_equal "Subscription", schema.subscription.graphql_name
497+
assert_equal [:query, :mutation, :subscription], calls
498+
end
479499
end

spec/graphql/subscriptions_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,8 @@ class Query < GraphQL::Schema::Object
205205
end
206206

207207
class Schema < GraphQL::Schema
208-
query(Query)
209-
subscription(Subscription)
208+
query { Query }
209+
subscription { Subscription }
210210
use InMemoryBackend::Subscriptions, extra: 123
211211
max_complexity(InMemoryBackend::MAX_COMPLEXITY)
212212
end

spec/support/dummy/schema.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,9 @@ def test; "Test"; end
536536
end
537537

538538
class Schema < GraphQL::Schema
539-
query DairyAppQuery
540-
mutation DairyAppMutation
541-
subscription Subscription
539+
query { DairyAppQuery }
540+
mutation { DairyAppMutation }
541+
subscription { Subscription }
542542
max_depth 5
543543
orphan_types Honey
544544
trace_with GraphQL::Tracing::CallLegacyTracers

0 commit comments

Comments
 (0)