graphql 0.17.2 → 0.18.0
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/lib/graphql.rb +1 -0
- data/lib/graphql/analysis/query_depth.rb +1 -1
- data/lib/graphql/base_type.rb +25 -1
- data/lib/graphql/define.rb +2 -0
- data/lib/graphql/define/assign_connection.rb +11 -0
- data/lib/graphql/define/assign_global_id_field.rb +11 -0
- data/lib/graphql/define/assign_object_field.rb +21 -20
- data/lib/graphql/define/defined_object_proxy.rb +2 -2
- data/lib/graphql/define/instance_definable.rb +13 -3
- data/lib/graphql/field.rb +1 -1
- data/lib/graphql/language/generation.rb +57 -6
- data/lib/graphql/language/lexer.rb +434 -212
- data/lib/graphql/language/lexer.rl +18 -0
- data/lib/graphql/language/nodes.rb +75 -0
- data/lib/graphql/language/parser.rb +853 -341
- data/lib/graphql/language/parser.y +114 -17
- data/lib/graphql/query.rb +15 -1
- data/lib/graphql/relay.rb +13 -0
- data/lib/graphql/relay/array_connection.rb +80 -0
- data/lib/graphql/relay/base_connection.rb +138 -0
- data/lib/graphql/relay/connection_field.rb +54 -0
- data/lib/graphql/relay/connection_type.rb +25 -0
- data/lib/graphql/relay/edge.rb +22 -0
- data/lib/graphql/relay/edge_type.rb +14 -0
- data/lib/graphql/relay/global_id_resolve.rb +15 -0
- data/lib/graphql/relay/global_node_identification.rb +124 -0
- data/lib/graphql/relay/mutation.rb +146 -0
- data/lib/graphql/relay/page_info.rb +13 -0
- data/lib/graphql/relay/relation_connection.rb +98 -0
- data/lib/graphql/schema.rb +3 -0
- data/lib/graphql/schema/printer.rb +12 -2
- data/lib/graphql/static_validation/message.rb +9 -5
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +3 -3
- data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +7 -7
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +5 -5
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +6 -6
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +17 -9
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +17 -6
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +2 -2
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +5 -5
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variables_are_used_and_defined.rb +12 -11
- data/lib/graphql/static_validation/type_stack.rb +33 -2
- data/lib/graphql/static_validation/validation_context.rb +5 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +16 -4
- data/spec/graphql/analysis/analyze_query_spec.rb +31 -2
- data/spec/graphql/analysis/query_complexity_spec.rb +24 -0
- data/spec/graphql/argument_spec.rb +1 -1
- data/spec/graphql/define/instance_definable_spec.rb +9 -0
- data/spec/graphql/field_spec.rb +1 -1
- data/spec/graphql/internal_representation/rewrite_spec.rb +3 -3
- data/spec/graphql/language/generation_spec.rb +25 -4
- data/spec/graphql/language/parser_spec.rb +116 -1
- data/spec/graphql/query_spec.rb +10 -0
- data/spec/graphql/relay/array_connection_spec.rb +164 -0
- data/spec/graphql/relay/connection_type_spec.rb +37 -0
- data/spec/graphql/relay/global_node_identification_spec.rb +149 -0
- data/spec/graphql/relay/mutation_spec.rb +55 -0
- data/spec/graphql/relay/page_info_spec.rb +106 -0
- data/spec/graphql/relay/relation_connection_spec.rb +348 -0
- data/spec/graphql/schema/printer_spec.rb +8 -0
- data/spec/graphql/schema/reduce_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +12 -6
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +7 -2
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +5 -3
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +4 -2
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +5 -2
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +10 -2
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +8 -4
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +6 -3
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +6 -3
- data/spec/spec_helper.rb +7 -0
- data/spec/support/dairy_app.rb +11 -10
- data/spec/support/star_wars_data.rb +65 -58
- data/spec/support/star_wars_schema.rb +192 -54
- metadata +84 -2
@@ -11,6 +11,7 @@ rule
|
|
11
11
|
definition:
|
12
12
|
operation_definition
|
13
13
|
| fragment_definition
|
14
|
+
| type_system_definition
|
14
15
|
|
15
16
|
operation_definition:
|
16
17
|
operation_type operation_name_opt variable_definitions_opt directives_list_opt selection_set {
|
@@ -52,7 +53,7 @@ rule
|
|
52
53
|
| variable_definitions_list variable_definition { val[0] << val[1] }
|
53
54
|
|
54
55
|
variable_definition:
|
55
|
-
VAR_SIGN name COLON
|
56
|
+
VAR_SIGN name COLON type default_value_opt {
|
56
57
|
return make_node(:VariableDefinition, {
|
57
58
|
name: val[1],
|
58
59
|
type: val[3],
|
@@ -61,12 +62,12 @@ rule
|
|
61
62
|
})
|
62
63
|
}
|
63
64
|
|
64
|
-
|
65
|
-
name
|
66
|
-
|
|
67
|
-
| RBRACKET
|
65
|
+
type:
|
66
|
+
name { return make_node(:TypeName, name: val[0])}
|
67
|
+
| type BANG { return make_node(:NonNullType, of_type: val[0]) }
|
68
|
+
| RBRACKET type LBRACKET { return make_node(:ListType, of_type: val[1]) }
|
68
69
|
|
69
|
-
|
70
|
+
default_value_opt:
|
70
71
|
/* none */ { return nil }
|
71
72
|
| EQUALS input_value { return val[1] }
|
72
73
|
|
@@ -116,14 +117,38 @@ rule
|
|
116
117
|
name_without_on
|
117
118
|
| ON
|
118
119
|
|
120
|
+
schema_keyword:
|
121
|
+
SCHEMA
|
122
|
+
| SCALAR
|
123
|
+
| TYPE
|
124
|
+
| IMPLEMENTS
|
125
|
+
| INTERFACE
|
126
|
+
| UNION
|
127
|
+
| ENUM
|
128
|
+
| INPUT
|
129
|
+
|
119
130
|
name_without_on:
|
120
131
|
IDENTIFIER
|
121
132
|
| FRAGMENT
|
122
133
|
| TRUE
|
123
134
|
| FALSE
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
135
|
+
| operation_type
|
136
|
+
| schema_keyword
|
137
|
+
|
138
|
+
enum_name: /* any identifier, but not "true", "false" or "null" */
|
139
|
+
IDENTIFIER
|
140
|
+
| FRAGMENT
|
141
|
+
| ON
|
142
|
+
| operation_type
|
143
|
+
| schema_keyword
|
144
|
+
|
145
|
+
name_list:
|
146
|
+
name { return [val[0].to_s]}
|
147
|
+
| name_list name { val[0] << val[1].to_s }
|
148
|
+
|
149
|
+
enum_name_list:
|
150
|
+
enum_name { return [val[0].to_s]}
|
151
|
+
| enum_name_list enum_name { return val[0] << val[1].to_s }
|
127
152
|
|
128
153
|
arguments_opt:
|
129
154
|
/* none */ { return [] }
|
@@ -171,14 +196,6 @@ rule
|
|
171
196
|
|
172
197
|
enum_value: enum_name { return make_node(:Enum, name: val[0], position_source: val[0])}
|
173
198
|
|
174
|
-
enum_name: /* any identifier, but not "true", "false" or "null" */
|
175
|
-
IDENTIFIER
|
176
|
-
| FRAGMENT
|
177
|
-
| ON
|
178
|
-
| QUERY
|
179
|
-
| MUTATION
|
180
|
-
| SUBSCRIPTION
|
181
|
-
|
182
199
|
directives_list_opt:
|
183
200
|
/* none */ { return [] }
|
184
201
|
| directives_list
|
@@ -221,6 +238,86 @@ rule
|
|
221
238
|
}
|
222
239
|
)
|
223
240
|
}
|
241
|
+
|
242
|
+
|
243
|
+
type_system_definition:
|
244
|
+
schema_definition
|
245
|
+
| type_definition
|
246
|
+
|
247
|
+
schema_definition:
|
248
|
+
SCHEMA RCURLY operation_type_definition_list LCURLY { return make_node(:SchemaDefinition, val[2]) }
|
249
|
+
|
250
|
+
operation_type_definition_list:
|
251
|
+
operation_type_definition
|
252
|
+
| operation_type_definition_list operation_type_definition { return val[0].merge(val[1]) }
|
253
|
+
|
254
|
+
operation_type_definition:
|
255
|
+
operation_type COLON name { return { val[0].to_s.to_sym => val[2] } }
|
256
|
+
|
257
|
+
type_definition:
|
258
|
+
scalar_type_definition
|
259
|
+
| object_type_definition
|
260
|
+
| interface_type_definition
|
261
|
+
| union_type_definition
|
262
|
+
| enum_type_definition
|
263
|
+
| input_object_type_definition
|
264
|
+
|
265
|
+
scalar_type_definition: SCALAR name { return make_node(:ScalarTypeDefinition, name: val[1]) }
|
266
|
+
|
267
|
+
object_type_definition:
|
268
|
+
TYPE name implements_opt RCURLY field_definition_list LCURLY {
|
269
|
+
return make_node(:ObjectTypeDefinition, name: val[1], interfaces: val[2], fields: val[4])
|
270
|
+
}
|
271
|
+
|
272
|
+
implements_opt:
|
273
|
+
/* none */ { return [] }
|
274
|
+
| IMPLEMENTS name_list { return val[1] }
|
275
|
+
|
276
|
+
input_value_definition:
|
277
|
+
name COLON type default_value_opt {
|
278
|
+
return make_node(:InputValueDefinition, name: val[0], type: val[2], default_value: val[3])
|
279
|
+
}
|
280
|
+
|
281
|
+
input_value_definition_list:
|
282
|
+
input_value_definition { return [val[0]] }
|
283
|
+
| input_value_definition_list input_value_definition { val[0] << val[1] }
|
284
|
+
|
285
|
+
arguments_definitions_opt:
|
286
|
+
/* none */ { return [] }
|
287
|
+
| RPAREN input_value_definition_list LPAREN { return val[1] }
|
288
|
+
|
289
|
+
field_definition:
|
290
|
+
name arguments_definitions_opt COLON type {
|
291
|
+
return make_node(:FieldDefinition, name: val[0], arguments: val[1], type: val[3])
|
292
|
+
}
|
293
|
+
|
294
|
+
field_definition_list:
|
295
|
+
field_definition { return [val[0]] }
|
296
|
+
| field_definition_list field_definition { val[0] << val[1] }
|
297
|
+
|
298
|
+
interface_type_definition:
|
299
|
+
INTERFACE name RCURLY field_definition_list LCURLY {
|
300
|
+
return make_node(:InterfaceTypeDefinition, name: val[1], fields: val[3])
|
301
|
+
}
|
302
|
+
|
303
|
+
union_members:
|
304
|
+
name { return [val[0].to_s]}
|
305
|
+
| union_members PIPE name { val[0] << val[2].to_s }
|
306
|
+
|
307
|
+
union_type_definition:
|
308
|
+
UNION name EQUALS union_members {
|
309
|
+
return make_node(:UnionTypeDefinition, name: val[1], types: val[3])
|
310
|
+
}
|
311
|
+
|
312
|
+
enum_type_definition:
|
313
|
+
ENUM name RCURLY enum_name_list LCURLY {
|
314
|
+
return make_node(:EnumTypeDefinition, name: val[1], values: val[3])
|
315
|
+
}
|
316
|
+
|
317
|
+
input_object_type_definition:
|
318
|
+
INPUT name RCURLY input_value_definition_list LCURLY {
|
319
|
+
return make_node(:InputObjectTypeDefinition, name: val[1], fields: val[3])
|
320
|
+
}
|
224
321
|
end
|
225
322
|
|
226
323
|
---- header ----
|
data/lib/graphql/query.rb
CHANGED
@@ -48,6 +48,8 @@ module GraphQL
|
|
48
48
|
@fragments[part.name] = part
|
49
49
|
elsif part.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
50
50
|
@operations[part.name] = part
|
51
|
+
else
|
52
|
+
raise GraphQL::ExecutionError, "GraphQL query cannot contain a schema definition"
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
@@ -71,7 +73,10 @@ module GraphQL
|
|
71
73
|
# If more than one operation is present, it must be named at runtime.
|
72
74
|
# @return [GraphQL::Language::Nodes::OperationDefinition, nil]
|
73
75
|
def selected_operation
|
74
|
-
@selected_operation ||=
|
76
|
+
@selected_operation ||= begin
|
77
|
+
perform_validation
|
78
|
+
@selected_operation
|
79
|
+
end
|
75
80
|
end
|
76
81
|
|
77
82
|
# Determine the values for variables of this query, using default values
|
@@ -116,9 +121,18 @@ module GraphQL
|
|
116
121
|
private
|
117
122
|
|
118
123
|
def perform_validation
|
124
|
+
@selected_operation = find_operation(@operations, @operation_name)
|
119
125
|
validation_result = schema.static_validator.validate(self)
|
120
126
|
@validation_errors = validation_result[:errors]
|
121
127
|
@internal_representation = validation_result[:irep]
|
128
|
+
if @validation_errors.none?
|
129
|
+
# Accessing variables will raise errors if there are any :S
|
130
|
+
variables
|
131
|
+
end
|
132
|
+
nil
|
133
|
+
rescue GraphQL::Query::OperationNameMissingError, GraphQL::Query::VariableValidationError => err
|
134
|
+
@validation_errors = [err.to_h]
|
135
|
+
@internal_representation = nil
|
122
136
|
nil
|
123
137
|
end
|
124
138
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
require 'graphql/relay/global_node_identification'
|
4
|
+
require 'graphql/relay/page_info'
|
5
|
+
require 'graphql/relay/edge'
|
6
|
+
require 'graphql/relay/edge_type'
|
7
|
+
require 'graphql/relay/base_connection'
|
8
|
+
require 'graphql/relay/array_connection'
|
9
|
+
require 'graphql/relay/relation_connection'
|
10
|
+
require 'graphql/relay/global_id_resolve'
|
11
|
+
require 'graphql/relay/mutation'
|
12
|
+
require 'graphql/relay/connection_field'
|
13
|
+
require 'graphql/relay/connection_type'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Relay
|
3
|
+
class ArrayConnection < BaseConnection
|
4
|
+
def cursor_from_node(item)
|
5
|
+
idx = starting_offset + sliced_nodes.find_index(item) + 1
|
6
|
+
Base64.strict_encode64(idx.to_s)
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_next_page
|
10
|
+
!!(first && sliced_nodes.count > limit)
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_previous_page
|
14
|
+
!!(last && starting_offset > 0)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# apply first / last limit results
|
20
|
+
def paged_nodes
|
21
|
+
@paged_nodes = begin
|
22
|
+
items = sliced_nodes
|
23
|
+
|
24
|
+
if limit
|
25
|
+
items.first(limit)
|
26
|
+
else
|
27
|
+
items
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Apply cursors to edges
|
33
|
+
def sliced_nodes
|
34
|
+
@sliced_nodes ||= begin
|
35
|
+
items = object
|
36
|
+
items[starting_offset..-1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def index_from_cursor(cursor)
|
41
|
+
Base64.decode64(cursor).to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def starting_offset
|
45
|
+
@starting_offset = if before
|
46
|
+
[previous_offset, 0].max
|
47
|
+
else
|
48
|
+
previous_offset
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def previous_offset
|
53
|
+
@previous_offset ||= if after
|
54
|
+
index_from_cursor(after)
|
55
|
+
elsif before
|
56
|
+
prev_page_size = [max_page_size, last].compact.min || 0
|
57
|
+
index_from_cursor(before) - prev_page_size - 1
|
58
|
+
else
|
59
|
+
0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def limit
|
64
|
+
@limit ||= begin
|
65
|
+
limit_from_arguments = if first
|
66
|
+
first
|
67
|
+
else
|
68
|
+
if previous_offset < 0
|
69
|
+
previous_offset + (last ? last : 0)
|
70
|
+
else
|
71
|
+
last
|
72
|
+
end
|
73
|
+
end
|
74
|
+
[limit_from_arguments, max_page_size].compact.min
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
BaseConnection.register_connection_implementation(Array, ArrayConnection)
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Relay
|
3
|
+
# Subclasses must implement:
|
4
|
+
# - {#cursor_from_node}, which returns an opaque cursor for the given item
|
5
|
+
# - {#sliced_nodes}, which slices by `before` & `after`
|
6
|
+
# - {#paged_nodes}, which applies `first` & `last` limits
|
7
|
+
#
|
8
|
+
# In a subclass, you have access to
|
9
|
+
# - {#object}, the object which the connection will wrap
|
10
|
+
# - {#first}, {#after}, {#last}, {#before} (arguments passed to the field)
|
11
|
+
# - {#max_page_size} (the specified maximum page size that can be returned from a connection)
|
12
|
+
#
|
13
|
+
class BaseConnection
|
14
|
+
# Just to encode data in the cursor, use something that won't conflict
|
15
|
+
CURSOR_SEPARATOR = "---"
|
16
|
+
|
17
|
+
# Map of collection class names -> connection_classes
|
18
|
+
# eg {"Array" => ArrayConnection}
|
19
|
+
CONNECTION_IMPLEMENTATIONS = {}
|
20
|
+
|
21
|
+
# Find a connection implementation suitable for exposing `items`
|
22
|
+
#
|
23
|
+
# @param [Object] A collection of items (eg, Array, AR::Relation)
|
24
|
+
# @return [subclass of BaseConnection] a connection Class for wrapping `items`
|
25
|
+
def self.connection_for_items(items)
|
26
|
+
# We check class membership by comparing class names rather than
|
27
|
+
# identity to prevent this from being broken by Rails autoloading.
|
28
|
+
# Changes to the source file for ItemsClass in Rails apps cause it to be
|
29
|
+
# reloaded as a new object, so if we were to use `is_a?` here, it would
|
30
|
+
# no longer match any registered custom connection types.
|
31
|
+
ancestor_names = items.class.ancestors.map(&:name)
|
32
|
+
implementation = CONNECTION_IMPLEMENTATIONS.find do |items_class_name, connection_class|
|
33
|
+
ancestor_names.include? items_class_name
|
34
|
+
end
|
35
|
+
if implementation.nil?
|
36
|
+
raise("No connection implementation to wrap #{items.class} (#{items})")
|
37
|
+
else
|
38
|
+
implementation[1]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add `connection_class` as the connection wrapper for `items_class`
|
43
|
+
# eg, `RelationConnection` is the implementation for `AR::Relation`
|
44
|
+
# @param [Class] A class representing a collection (eg, Array, AR::Relation)
|
45
|
+
# @param [Class] A class implementing Connection methods
|
46
|
+
def self.register_connection_implementation(items_class, connection_class)
|
47
|
+
CONNECTION_IMPLEMENTATIONS[items_class.name] = connection_class
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :object, :arguments, :max_page_size, :parent
|
51
|
+
|
52
|
+
# Make a connection, wrapping `object`
|
53
|
+
# @param The collection of results
|
54
|
+
# @param Query arguments
|
55
|
+
# @param max_page_size [Int] The maximum number of results to return
|
56
|
+
# @param parent [Object] The object which this collection belongs to
|
57
|
+
def initialize(object, arguments, max_page_size: nil, parent: nil)
|
58
|
+
@object = object
|
59
|
+
@arguments = arguments
|
60
|
+
@max_page_size = max_page_size
|
61
|
+
@parent = parent
|
62
|
+
end
|
63
|
+
|
64
|
+
# Provide easy access to provided arguments:
|
65
|
+
METHODS_FROM_ARGUMENTS = [:first, :after, :last, :before, :order]
|
66
|
+
|
67
|
+
# @!method first
|
68
|
+
# The value passed as `first:`, if there was one
|
69
|
+
# @!method after
|
70
|
+
# The value passed as `after:`, if there was one
|
71
|
+
# @!method last
|
72
|
+
# The value passed as `last:`, if there was one
|
73
|
+
# @!method before
|
74
|
+
# The value passed as `before:`, if there was one
|
75
|
+
# @!method order
|
76
|
+
# The value passed as `order:`, if there was one
|
77
|
+
METHODS_FROM_ARGUMENTS.each do |arg_name|
|
78
|
+
define_method(arg_name) do
|
79
|
+
arguments[arg_name]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# These are the items to render for this connection,
|
84
|
+
# probably wrapped by {GraphQL::Relay::Edge}
|
85
|
+
def edge_nodes
|
86
|
+
@edge_nodes ||= paged_nodes
|
87
|
+
end
|
88
|
+
|
89
|
+
# Support the `pageInfo` field
|
90
|
+
def page_info
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
# Used by `pageInfo`
|
95
|
+
def has_next_page
|
96
|
+
!!(first && sliced_nodes.count > first)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Used by `pageInfo`
|
100
|
+
def has_previous_page
|
101
|
+
!!(last && sliced_nodes.count > last)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Used by `pageInfo`
|
105
|
+
def start_cursor
|
106
|
+
if start_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).first
|
107
|
+
return cursor_from_node(start_node)
|
108
|
+
else
|
109
|
+
return nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Used by `pageInfo`
|
114
|
+
def end_cursor
|
115
|
+
if end_node = (respond_to?(:paged_nodes_array, true) ? paged_nodes_array : paged_nodes).last
|
116
|
+
return cursor_from_node(end_node)
|
117
|
+
else
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# An opaque operation which returns a connection-specific cursor.
|
123
|
+
def cursor_from_node(object)
|
124
|
+
raise NotImplementedError, "must return a cursor for this object/connection pair"
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def paged_nodes
|
130
|
+
raise NotImplementedError, "must return items for this connection after paging"
|
131
|
+
end
|
132
|
+
|
133
|
+
def sliced_nodes
|
134
|
+
raise NotImplementedError, "must return all items for this connection after chopping off first and last"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|