Skip to content

Commit 9171f25

Browse files
committed
refactor(Schema) create schemas with Schema.define
1 parent 0288921 commit 9171f25

11 files changed

+154
-93
lines changed

lib/graphql.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
require "json"
22
require "set"
33
require "singleton"
4-
require "forwardable"
54

65
module GraphQL
76
class Error < StandardError

lib/graphql/query.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ def initialize(schema, query_string = nil, document: nil, context: nil, variable
5050
@operations = {}
5151
@provided_variables = variables
5252
@query_string = query_string
53-
5453
@document = document || GraphQL.parse(query_string)
5554
@document.definitions.each do |part|
5655
if part.is_a?(GraphQL::Language::Nodes::FragmentDefinition)

lib/graphql/schema.rb

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,83 @@
1111

1212
module GraphQL
1313
# A GraphQL schema which may be queried with {GraphQL::Query}.
14+
#
15+
# The {Schema} contains:
16+
#
17+
# - types for exposing your application
18+
# - query analyzers for assessing incoming queries (including max depth & max complexity restrictions)
19+
# - execution strategies for running incoming queries
20+
# - middleware for interacting with execution
21+
#
22+
# Schemas start with root types, {Schema#query}, {Schema#mutation} and {Schema#subscription}.
23+
# The schema will traverse the tree of fields & types, using those as starting points.
24+
# Any undiscoverable types may be provided with the `types` configuration.
25+
#
26+
# Schemas can restrict large incoming queries with `max_depth` and `max_complexity` configurations.
27+
# (These configurations can be overridden by specific calls to {Schema#execute})
28+
#
29+
# Schemas can specify how queries should be executed against them.
30+
# `query_execution_strategy`, `mutation_execution_strategy` and `subscription_execution_strategy`
31+
# each apply to corresponding root types.
32+
#
33+
# A schema accepts a `Relay::GlobalNodeIdentification` instance for use with Relay IDs.
34+
#
35+
# @example defining a schema
36+
# MySchema = GraphQL::Schema.define do
37+
# query QueryType
38+
# middleware PermissionMiddleware
39+
# rescue_from(ActiveRecord::RecordNotFound) { "Not found" }
40+
# # If types are only connected by way of interfaces, they must be added here
41+
# orphan_types ImageType, AudioType
42+
# end
43+
#
1444
class Schema
15-
extend Forwardable
45+
include GraphQL::Define::InstanceDefinable
46+
accepts_definitions \
47+
:query, :mutation, :subscription,
48+
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
49+
:max_depth, :max_complexity,
50+
:node_identification,
51+
:orphan_types,
52+
query_analyzer: -> (schema, analyzer) { schema.query_analyzers << analyzer },
53+
middleware: -> (schema, middleware) { schema.middleware << middleware },
54+
rescue_from: -> (schema, err_class, &block) { schema.rescue_from(err_class, &block)}
55+
56+
lazy_defined_attr_accessor \
57+
:query, :mutation, :subscription,
58+
:query_execution_strategy, :mutation_execution_strategy, :subscription_execution_strategy,
59+
:max_depth, :max_complexity,
60+
:node_identification,
61+
:orphan_types,
62+
:query_analyzers, :middleware
63+
1664

1765
DIRECTIVES = [GraphQL::Directive::SkipDirective, GraphQL::Directive::IncludeDirective]
1866
DYNAMIC_FIELDS = ["__type", "__typename", "__schema"]
1967

20-
attr_reader :query, :mutation, :subscription, :directives, :static_validator, :query_analyzers
21-
attr_accessor :max_depth
22-
attr_accessor :max_complexity
23-
24-
# Override these if you don't want the default executor:
25-
attr_accessor :query_execution_strategy,
26-
:mutation_execution_strategy,
27-
:subscription_execution_strategy
68+
attr_reader :directives, :static_validator
2869

2970
# @return [GraphQL::Relay::GlobalNodeIdentification] the node identification instance for this schema, when using Relay
30-
attr_accessor :node_identification
71+
def node_identification
72+
ensure_defined
73+
@node_identification
74+
end
3175

3276
# @return [Array<#call>] Middlewares suitable for MiddlewareChain, applied to fields during execution
33-
attr_reader :middleware
77+
def middleware
78+
ensure_defined
79+
@middleware
80+
end
3481

3582
# @param query [GraphQL::ObjectType] the query root for the schema
3683
# @param mutation [GraphQL::ObjectType] the mutation root for the schema
3784
# @param subscription [GraphQL::ObjectType] the subscription root for the schema
3885
# @param max_depth [Integer] maximum query nesting (if it's greater, raise an error)
3986
# @param types [Array<GraphQL::BaseType>] additional types to include in this schema
40-
def initialize(query:, mutation: nil, subscription: nil, max_depth: nil, max_complexity: nil, types: [])
87+
def initialize(query: nil, mutation: nil, subscription: nil, max_depth: nil, max_complexity: nil, types: [])
88+
if query
89+
warn("Schema.new is deprecated, use Schema.define instead")
90+
end
4191
@query = query
4292
@mutation = mutation
4393
@subscription = subscription
@@ -50,17 +100,26 @@ def initialize(query:, mutation: nil, subscription: nil, max_depth: nil, max_com
50100
@middleware = [@rescue_middleware]
51101
@query_analyzers = []
52102
# Default to the built-in execution strategy:
53-
self.query_execution_strategy = GraphQL::Query::SerialExecution
54-
self.mutation_execution_strategy = GraphQL::Query::SerialExecution
55-
self.subscription_execution_strategy = GraphQL::Query::SerialExecution
103+
@query_execution_strategy = GraphQL::Query::SerialExecution
104+
@mutation_execution_strategy = GraphQL::Query::SerialExecution
105+
@subscription_execution_strategy = GraphQL::Query::SerialExecution
106+
end
107+
108+
def rescue_from(*args, &block)
109+
ensure_defined
110+
@rescue_middleware.rescue_from(*args, &block)
56111
end
57112

58-
def_delegators :@rescue_middleware, :rescue_from, :remove_handler
113+
def remove_handler(*args, &block)
114+
ensure_defined
115+
@rescue_middleware.remove_handler(*args, &block)
116+
end
59117

60118
# @return [GraphQL::Schema::TypeMap] `{ name => type }` pairs of types in this schema
61119
def types
62120
@types ||= begin
63-
all_types = @orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
121+
ensure_defined
122+
all_types = orphan_types + [query, mutation, subscription, GraphQL::Introspection::SchemaType]
64123
GraphQL::Schema::ReduceTypes.reduce(all_types.compact)
65124
end
66125
end
@@ -69,13 +128,14 @@ def types
69128
# See {Query#initialize} for arguments.
70129
# @return [Hash] query result, ready to be serialized as JSON
71130
def execute(*args)
72-
query = GraphQL::Query.new(self, *args)
73-
query.result
131+
query_obj = GraphQL::Query.new(self, *args)
132+
query_obj.result
74133
end
75134

76135
# Resolve field named `field_name` for type `parent_type`.
77136
# Handles dynamic fields `__typename`, `__type` and `__schema`, too
78137
def get_field(parent_type, field_name)
138+
ensure_defined
79139
defined_field = parent_type.get_field(field_name)
80140
if defined_field
81141
defined_field
@@ -91,12 +151,14 @@ def get_field(parent_type, field_name)
91151
end
92152

93153
def type_from_ast(ast_node)
154+
ensure_defined
94155
GraphQL::Schema::TypeExpression.build_type(self, ast_node)
95156
end
96157

97158
# @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
98159
# @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
99160
def possible_types(type_defn)
161+
ensure_defined
100162
@interface_possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
101163
@interface_possible_types.possible_types(type_defn)
102164
end

spec/graphql/relay/array_connection_spec.rb

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,66 +33,66 @@ def get_last_cursor(result)
3333
|}
3434

3535
it 'limits the result' do
36-
result = query(query_string, "first" => 2)
36+
result = star_wars_query(query_string, "first" => 2)
3737
number_of_ships = get_names(result).length
3838
assert_equal(2, number_of_ships)
3939
assert_equal(true, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
4040
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
4141
assert_equal("MQ==", result["data"]["rebels"]["ships"]["pageInfo"]["startCursor"])
4242
assert_equal("Mg==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
4343

44-
result = query(query_string, "first" => 3)
44+
result = star_wars_query(query_string, "first" => 3)
4545
number_of_ships = get_names(result).length
4646
assert_equal(3, number_of_ships)
4747
end
4848

4949
it 'provides pageInfo' do
50-
result = query(query_string, "first" => 2)
50+
result = star_wars_query(query_string, "first" => 2)
5151
assert_equal(true, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
5252
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
5353
assert_equal("MQ==", result["data"]["rebels"]["ships"]["pageInfo"]["startCursor"])
5454
assert_equal("Mg==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
5555

56-
result = query(query_string, "first" => 100)
56+
result = star_wars_query(query_string, "first" => 100)
5757
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
5858
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
5959
assert_equal("MQ==", result["data"]["rebels"]["ships"]["pageInfo"]["startCursor"])
6060
assert_equal("NQ==", result["data"]["rebels"]["ships"]["pageInfo"]["endCursor"])
6161
end
6262

6363
it 'slices the result' do
64-
result = query(query_string, "first" => 1)
64+
result = star_wars_query(query_string, "first" => 1)
6565
assert_equal(["X-Wing"], get_names(result))
6666

6767
# After the last result, find the next 2:
6868
last_cursor = get_last_cursor(result)
6969

70-
result = query(query_string, "after" => last_cursor, "first" => 2)
70+
result = star_wars_query(query_string, "after" => last_cursor, "first" => 2)
7171
assert_equal(["Y-Wing", "A-Wing"], get_names(result))
7272

7373
# After the last result, find the next 2:
7474
last_cursor = get_last_cursor(result)
7575

76-
result = query(query_string, "after" => last_cursor, "first" => 2)
76+
result = star_wars_query(query_string, "after" => last_cursor, "first" => 2)
7777
assert_equal(["Millenium Falcon", "Home One"], get_names(result))
7878

79-
result = query(query_string, "before" => last_cursor, "last" => 2)
79+
result = star_wars_query(query_string, "before" => last_cursor, "last" => 2)
8080
assert_equal(["X-Wing", "Y-Wing"], get_names(result))
8181
end
8282

8383
it 'applies custom arguments' do
84-
result = query(query_string, "nameIncludes" => "Wing", "first" => 2)
84+
result = star_wars_query(query_string, "nameIncludes" => "Wing", "first" => 2)
8585
names = get_names(result)
8686
assert_equal(2, names.length)
8787

8888
after = get_last_cursor(result)
89-
result = query(query_string, "nameIncludes" => "Wing", "after" => after, "first" => 3)
89+
result = star_wars_query(query_string, "nameIncludes" => "Wing", "after" => after, "first" => 3)
9090
names = get_names(result)
9191
assert_equal(1, names.length)
9292
end
9393

9494
it 'works without first/last/after/before' do
95-
result = query(query_string)
95+
result = star_wars_query(query_string)
9696

9797
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasNextPage"])
9898
assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
@@ -130,33 +130,33 @@ def get_page_info(result)
130130
|}
131131

132132
it "applies to queries by `first`" do
133-
result = query(query_string, "first" => 100)
133+
result = star_wars_query(query_string, "first" => 100)
134134
assert_equal(["Yavin", "Echo Base"], get_names(result))
135135
assert_equal(true, get_page_info(result)["hasNextPage"])
136136

137137
# Max page size is applied _without_ `first`, also
138-
result = query(query_string)
138+
result = star_wars_query(query_string)
139139
assert_equal(["Yavin", "Echo Base"], get_names(result))
140140
assert_equal(false, get_page_info(result)["hasNextPage"], "hasNextPage is false when first is not specified")
141141
end
142142

143143
it "applies to queries by `last`" do
144144
last_cursor = "Ng=="
145145
second_to_last_two_names = ["Death Star", "Shield Generator"]
146-
result = query(query_string, "last" => 100, "before" => last_cursor)
146+
result = star_wars_query(query_string, "last" => 100, "before" => last_cursor)
147147
assert_equal(second_to_last_two_names, get_names(result))
148148
assert_equal(true, get_page_info(result)["hasPreviousPage"])
149149

150-
result = query(query_string, "before" => last_cursor)
150+
result = star_wars_query(query_string, "before" => last_cursor)
151151
assert_equal(second_to_last_two_names, get_names(result))
152152
assert_equal(false, get_page_info(result)["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
153153

154154
third_cursor = "Mw=="
155155
first_and_second_names = ["Yavin", "Echo Base"]
156-
result = query(query_string, "last" => 100, "before" => third_cursor)
156+
result = star_wars_query(query_string, "last" => 100, "before" => third_cursor)
157157
assert_equal(first_and_second_names, get_names(result))
158158

159-
result = query(query_string, "before" => third_cursor)
159+
result = star_wars_query(query_string, "before" => third_cursor)
160160
assert_equal(first_and_second_names, get_names(result))
161161
end
162162
end

spec/graphql/relay/connection_type_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
|}
2525

2626
it "uses the custom edge and custom connection" do
27-
result = query(query_string)
27+
result = star_wars_query(query_string)
2828
bases = result["data"]["rebels"]["basesWithCustomEdge"]
2929
assert_equal 300, bases["totalCountTimes100"]
3030
assert_equal 'basesWithCustomEdge', bases["fieldName"]

spec/graphql/relay/global_node_identification_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
describe 'NodeField' do
66
it 'finds objects by id' do
77
global_id = node_identification.to_global_id("Faction", "1")
8-
result = query(%|{
8+
result = star_wars_query(%|{
99
node(id: "#{global_id}") {
1010
id,
1111
... on Faction {
@@ -107,15 +107,15 @@
107107

108108
describe "generating IDs" do
109109
it "Applies custom-defined ID generation" do
110-
result = query(%| { largestBase { id } }|)
110+
result = star_wars_query(%| { largestBase { id } }|)
111111
generated_id = result["data"]["largestBase"]["id"]
112112
assert_equal "Base/3", generated_id
113113
end
114114
end
115115

116116
describe "fetching by ID" do
117117
it "Deconstructs the ID by the custom proc" do
118-
result = query(%| { node(id: "Base/1") { ... on Base { name } } }|)
118+
result = star_wars_query(%| { node(id: "Base/1") { ... on Base { name } } }|)
119119
base_name = result["data"]["node"]["name"]
120120
assert_equal "Yavin", base_name
121121
end

spec/graphql/relay/mutation_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
end
2727

2828
it "returns the result & clientMutationId" do
29-
result = query(query_string, "clientMutationId" => "1234")
29+
result = star_wars_query(query_string, "clientMutationId" => "1234")
3030
expected = {"data" => {
3131
"introduceShip" => {
3232
"clientMutationId" => "1234",
@@ -43,7 +43,7 @@
4343
end
4444

4545
it "doesn't require a clientMutationId to perform mutations" do
46-
result = query(query_string)
46+
result = star_wars_query(query_string)
4747
new_ship_name = result["data"]["introduceShip"]["shipEdge"]["node"]["name"]
4848
assert_equal("Bagel", new_ship_name)
4949
end

0 commit comments

Comments
 (0)