relay-rb 0.0.1
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 +7 -0
- data/Rakefile +26 -0
- data/lib/relay/connection/array.rb +54 -0
- data/lib/relay/connection/connection.rb +78 -0
- data/lib/relay/node/global_id_field.rb +38 -0
- data/lib/relay/node/plural_identifying_root_field.rb +35 -0
- data/lib/relay/node.rb +50 -0
- data/lib/relay/version.rb +3 -0
- data/lib/relay.rb +6 -0
- data/spec/connection/connection_spec.rb +85 -0
- data/spec/node/data.rb +69 -0
- data/spec/node/data_global.rb +78 -0
- data/spec/node/node_global_spec.rb +50 -0
- data/spec/node/node_plural_spec.rb +51 -0
- data/spec/node/node_spec.rb +106 -0
- data/spec/spec_helper.rb +96 -0
- data/spec/star_wars/connection_spec.rb +251 -0
- data/spec/star_wars/data.rb +54 -0
- data/spec/star_wars/object_identification_spec.rb +139 -0
- data/spec/star_wars/schema.rb +115 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7c976d6b1f308d77fad6f43623d821150b45df13
|
4
|
+
data.tar.gz: d396a7d982e23b6ca71bafbbdbc438b444c0ea6b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 914555d8c843092f3a6fbe1cec290ae032784a4296b32eaf1245d65429693a9305e2e5e5a55ac84f54ba2a45b38cd7fc1813105b6410c4ca452e3c0fc3c29651
|
7
|
+
data.tar.gz: 70c0950fcf1b438467aef315b0f572febb7c17609cc4e9d6e00a0f23ea338e5a1b58d10e3560520e4dc47c5224e9fd96899ac1a67df10ebfcd7f7cc76fbec079
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Relay Ruby'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
14
|
+
end
|
15
|
+
|
16
|
+
Bundler::GemHelper.install_tasks
|
17
|
+
|
18
|
+
require 'rspec'
|
19
|
+
require 'rspec/core/rake_task'
|
20
|
+
|
21
|
+
RSpec::Core::RakeTask.new(:test) do |test|
|
22
|
+
test.verbose = false
|
23
|
+
test.rspec_opts = %w[--color --format documentation]
|
24
|
+
end
|
25
|
+
|
26
|
+
task default: :test
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Relay
|
4
|
+
module Connection
|
5
|
+
|
6
|
+
ARRAY_CONNECTION_PREFIX = 'arrayconnection'
|
7
|
+
|
8
|
+
Edge = Struct.new('Edge', :cursor, :node)
|
9
|
+
PageInfo = Struct.new('PageInfo', :startCursor, :endCursor, :hasPreviousPage, :hasNextPage)
|
10
|
+
ArrayConnection = Struct.new('ArrayConnection', :edges, :pageInfo)
|
11
|
+
EmptyConnection = ArrayConnection.new([], PageInfo.new(nil, nil, false, false))
|
12
|
+
|
13
|
+
def self.connection_from_array(data, args)
|
14
|
+
edges = data.each_with_index.map { |item, i| Edge.new(offset_to_cursor(i), item) }
|
15
|
+
|
16
|
+
start = [get_offset(args[:after], -1), -1].max + 1
|
17
|
+
finish = [get_offset(args[:before], edges.size + 1), edges.size + 1].min
|
18
|
+
|
19
|
+
edges = edges.slice(start, finish)
|
20
|
+
|
21
|
+
return EmptyConnection if edges.size == 0
|
22
|
+
|
23
|
+
first_preslice_cursor = edges.first[:cursor]
|
24
|
+
last_preslice_cursor = edges.last[:cursor]
|
25
|
+
|
26
|
+
edges = edges.slice(0, args[:first]) unless args[:first].nil?
|
27
|
+
egdes = edges.slice!(- args[:last], edges.size) unless args[:last].nil?
|
28
|
+
|
29
|
+
return EmptyConnection if edges.size == 0
|
30
|
+
|
31
|
+
ArrayConnection.new(edges, PageInfo.new(
|
32
|
+
edges.first[:cursor],
|
33
|
+
edges.last[:cursor],
|
34
|
+
edges.first[:cursor] != first_preslice_cursor,
|
35
|
+
edges.last[:cursor] != last_preslice_cursor
|
36
|
+
))
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.offset_to_cursor(index)
|
40
|
+
Base64.strict_encode64([ARRAY_CONNECTION_PREFIX, index].join(':'))
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.cursor_to_offset(cursor)
|
44
|
+
Base64.strict_decode64(cursor).split(':').last.to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.get_offset(cursor, default_offset)
|
48
|
+
return default_offset unless cursor
|
49
|
+
offset = cursor_to_offset(cursor)
|
50
|
+
offset.nil? ? default_offset : offset
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'array'
|
2
|
+
|
3
|
+
module Relay
|
4
|
+
module Connection
|
5
|
+
|
6
|
+
ConnectionArguments = [
|
7
|
+
GraphQL::GraphQLArgument.new(:before, GraphQL::GraphQLString),
|
8
|
+
GraphQL::GraphQLArgument.new(:after, GraphQL::GraphQLString),
|
9
|
+
GraphQL::GraphQLArgument.new(:first, GraphQL::GraphQLInt),
|
10
|
+
GraphQL::GraphQLArgument.new(:last, GraphQL::GraphQLInt)
|
11
|
+
]
|
12
|
+
|
13
|
+
class ConnectionConfiguration < GraphQL::Configuration::Base
|
14
|
+
slot :name, String
|
15
|
+
slot :node_type, GraphQL::GraphQLObjectType
|
16
|
+
slot :edge_fields, [GraphQL::GraphQLField], singular: :edge_field
|
17
|
+
slot :connection_fields, [GraphQL::GraphQLField], singular: :connection_field
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.connection_definitions(configuration)
|
21
|
+
name, node_type = configuration.name, configuration.node_type
|
22
|
+
|
23
|
+
edge_type = GraphQL::GraphQLObjectType.new do
|
24
|
+
name name + 'Edge'
|
25
|
+
description 'An edge in a connection'
|
26
|
+
|
27
|
+
field :node, node_type do
|
28
|
+
description 'The item at the end of the edge'
|
29
|
+
end
|
30
|
+
|
31
|
+
field :cursor, ! GraphQL::GraphQLString do
|
32
|
+
description 'A cursor for use in pagination'
|
33
|
+
end
|
34
|
+
|
35
|
+
fields configuration.edge_fields
|
36
|
+
end
|
37
|
+
|
38
|
+
connection_type = GraphQL::GraphQLObjectType.new do
|
39
|
+
name name + 'Connection'
|
40
|
+
description 'A connection to a list of items.'
|
41
|
+
|
42
|
+
field :pageInfo, !PageInfoType do
|
43
|
+
description 'Information to aid in pagination.'
|
44
|
+
end
|
45
|
+
|
46
|
+
field :edges, +edge_type do
|
47
|
+
description 'Information to aid in pagination.'
|
48
|
+
end
|
49
|
+
|
50
|
+
fields configuration.connection_fields
|
51
|
+
end
|
52
|
+
|
53
|
+
return edge_type, connection_type
|
54
|
+
end
|
55
|
+
|
56
|
+
PageInfoType = GraphQL::GraphQLObjectType.new do
|
57
|
+
name 'PageInfo'
|
58
|
+
description 'Information about pagination in a connection.'
|
59
|
+
|
60
|
+
field :hasNextPage, !GraphQL::GraphQLBoolean do
|
61
|
+
description 'When paginating forwards, are there more items?'
|
62
|
+
end
|
63
|
+
|
64
|
+
field :hasPreviousPage, !GraphQL::GraphQLBoolean do
|
65
|
+
description 'When paginating backwards, are there more items?'
|
66
|
+
end
|
67
|
+
|
68
|
+
field :startCursor, GraphQL::GraphQLString do
|
69
|
+
description 'When paginating backwards, the cursor to continue.'
|
70
|
+
end
|
71
|
+
|
72
|
+
field :endCursor, GraphQL::GraphQLString do
|
73
|
+
description 'When paginating forwards, the cursor to continue.'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'graphql'
|
2
|
+
|
3
|
+
module Relay
|
4
|
+
|
5
|
+
|
6
|
+
GLOBAL_ID_FIELD_DEFAULT_RESOLVE = -> (object) { object.id }
|
7
|
+
|
8
|
+
class GlobalIDFieldConfiguration < GraphQL::Configuration::Base
|
9
|
+
slot :name, String, coerce: -> (v) { v.to_s }
|
10
|
+
slot :type_name, String, coerce: -> (v) { v.to_s }
|
11
|
+
slot :resolve_id, Proc, null: true
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
class GraphQL::GraphQLObjectTypeConfiguration
|
16
|
+
|
17
|
+
def global_id_field(*args, &block)
|
18
|
+
configuration = GlobalIDFieldConfiguration.new(*args, &block)
|
19
|
+
|
20
|
+
resolve_id = configuration.resolve_id || GLOBAL_ID_FIELD_DEFAULT_RESOLVE
|
21
|
+
|
22
|
+
global_id_field = GraphQL::GraphQLFieldConfiguration.new do
|
23
|
+
name configuration.name
|
24
|
+
|
25
|
+
type !GraphQL::GraphQLID
|
26
|
+
|
27
|
+
resolve lambda { |object|
|
28
|
+
Relay::Node.to_global_id(configuration.type_name, resolve_id.call(object))
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
field(global_id_field)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Relay
|
2
|
+
module Node
|
3
|
+
|
4
|
+
class PluralIdentifyingRootFieldConfiguration < GraphQL::Configuration::Base
|
5
|
+
slot :name, String, coerce: -> (v) { v.to_s }
|
6
|
+
slot :input_type, GraphQL::GraphQLInputType
|
7
|
+
slot :output_type, GraphQL::GraphQLOutputType
|
8
|
+
slot :argument_name, String, coerce: -> (v) { v.to_s }
|
9
|
+
slot :resolve_single_input, Proc
|
10
|
+
end
|
11
|
+
|
12
|
+
class GraphQL::GraphQLObjectTypeConfiguration
|
13
|
+
|
14
|
+
def plural_identifying_root_field(*args, &block)
|
15
|
+
configuration = PluralIdentifyingRootFieldConfiguration.new(*args, &block)
|
16
|
+
|
17
|
+
plural_identifying_root_field = GraphQL::GraphQLFieldConfiguration.new do
|
18
|
+
name configuration.name
|
19
|
+
|
20
|
+
type +configuration.output_type
|
21
|
+
|
22
|
+
arg configuration.argument_name, !+!configuration.input_type
|
23
|
+
|
24
|
+
resolve lambda { |object, params|
|
25
|
+
params[configuration.argument_name.to_sym].map { |param| configuration.resolve_single_input.call(param) }
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
field(plural_identifying_root_field)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/relay/node.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'graphql'
|
2
|
+
require 'base64'
|
3
|
+
require_relative 'node/global_id_field'
|
4
|
+
require_relative 'node/plural_identifying_root_field'
|
5
|
+
|
6
|
+
module Relay
|
7
|
+
|
8
|
+
module Node
|
9
|
+
|
10
|
+
def self.definitions(fetcher, resolver)
|
11
|
+
|
12
|
+
interface = GraphQL::GraphQLInterfaceType.new do
|
13
|
+
name 'Node'
|
14
|
+
description 'An object with ID'
|
15
|
+
|
16
|
+
field :id, ! GraphQL::GraphQLID do
|
17
|
+
description 'The id of the object'
|
18
|
+
end
|
19
|
+
|
20
|
+
resolve_type resolver
|
21
|
+
end
|
22
|
+
|
23
|
+
field = GraphQL::GraphQLField.new do
|
24
|
+
name 'node'
|
25
|
+
description 'Fetches an object given its ID'
|
26
|
+
type interface
|
27
|
+
|
28
|
+
arg :id, ! GraphQL::GraphQLID do
|
29
|
+
description 'The ID of an object'
|
30
|
+
end
|
31
|
+
|
32
|
+
resolve lambda { |root, params, info, *args|
|
33
|
+
fetcher.call(params[:id], info)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
{ interface: interface, field: field }
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.to_global_id(type, id)
|
41
|
+
Base64.strict_encode64([type, id].join(':'))
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.from_global_id(global_id)
|
45
|
+
return Base64.strict_decode64(global_id).split(':')
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/relay.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'relay'
|
2
|
+
|
3
|
+
RSpec.describe 'Relay Connection' do
|
4
|
+
|
5
|
+
User = Struct.new('UserInRelayConnection', :name)
|
6
|
+
|
7
|
+
Users = [
|
8
|
+
User.new('Dan'),
|
9
|
+
User.new('Nick'),
|
10
|
+
User.new('Lee'),
|
11
|
+
User.new('Joe'),
|
12
|
+
User.new('Tim')
|
13
|
+
]
|
14
|
+
|
15
|
+
friend_edge, friend_connection = Relay::Connection.connection_definitions(
|
16
|
+
Relay::Connection::ConnectionConfiguration.new do
|
17
|
+
name 'Friend'
|
18
|
+
node_type -> { ConnectionUserType }
|
19
|
+
|
20
|
+
edge_field :friendship_time, GraphQL::GraphQLString do
|
21
|
+
resolve -> { 'Yesterday' }
|
22
|
+
end
|
23
|
+
|
24
|
+
connection_field :total_count, GraphQL::GraphQLInt do
|
25
|
+
resolve -> { Users.size }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
)
|
29
|
+
|
30
|
+
ConnectionUserType = GraphQL::GraphQLObjectType.new do
|
31
|
+
name 'User'
|
32
|
+
|
33
|
+
field :name, GraphQL::GraphQLString
|
34
|
+
|
35
|
+
field :friends do
|
36
|
+
type friend_connection
|
37
|
+
args Relay::Connection::ConnectionArguments
|
38
|
+
resolve lambda { |user, params|
|
39
|
+
Relay::Connection.connection_from_array(Users, params)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ConnectionQueryType = GraphQL::GraphQLObjectType.new do
|
45
|
+
name 'Query'
|
46
|
+
|
47
|
+
field :user, ConnectionUserType do
|
48
|
+
resolve -> { Users[0] }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ConnectionSchema = GraphQL::GraphQLSchema.new do
|
53
|
+
query ConnectionQueryType
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def q1
|
58
|
+
%Q(
|
59
|
+
query FriendsQuery {
|
60
|
+
user {
|
61
|
+
name
|
62
|
+
friends(first: 2, after: "YXJyYXljb25uZWN0aW9uOjM=") {
|
63
|
+
total_count
|
64
|
+
edges {
|
65
|
+
friendship_time
|
66
|
+
cursor
|
67
|
+
node {
|
68
|
+
name
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
it "Should include connections and edge fields" do
|
79
|
+
document = GraphQL::Language.parse(q1)
|
80
|
+
executor = GraphQL::Executor.new(document, ConnectionSchema)
|
81
|
+
puts executor.execute({})
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
end
|
data/spec/node/data.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Relay
|
2
|
+
module Node
|
3
|
+
module Data
|
4
|
+
|
5
|
+
UserStruct = Struct.new('User', :id, :name)
|
6
|
+
|
7
|
+
Users = {
|
8
|
+
'1' => UserStruct.new('1', 'John Doe'),
|
9
|
+
'2' => UserStruct.new('2', 'Jane Smith')
|
10
|
+
}
|
11
|
+
|
12
|
+
PhotoStruct = Struct.new('Photo', :id, :width)
|
13
|
+
|
14
|
+
Photos = {
|
15
|
+
'3' => PhotoStruct.new('3', 300),
|
16
|
+
'4' => PhotoStruct.new('4', 400)
|
17
|
+
}
|
18
|
+
|
19
|
+
fetcher = lambda { |id, info|
|
20
|
+
if Users[id.to_s]
|
21
|
+
return Users[id.to_s]
|
22
|
+
end
|
23
|
+
if Photos[id.to_s]
|
24
|
+
return Photos[id.to_s]
|
25
|
+
end
|
26
|
+
}
|
27
|
+
|
28
|
+
resolver = lambda { |root|
|
29
|
+
if Users[root.id.to_s]
|
30
|
+
return UserType
|
31
|
+
end
|
32
|
+
|
33
|
+
if Photos[root.id.to_s]
|
34
|
+
return PhotoType
|
35
|
+
end
|
36
|
+
}
|
37
|
+
|
38
|
+
definitions = Relay::Node.definitions(fetcher, resolver)
|
39
|
+
|
40
|
+
UserType = GraphQL::GraphQLObjectType.new do
|
41
|
+
name 'User'
|
42
|
+
|
43
|
+
field :id, ! GraphQL::GraphQLID
|
44
|
+
field :name, GraphQL::GraphQLString
|
45
|
+
|
46
|
+
interface definitions[:interface]
|
47
|
+
end
|
48
|
+
|
49
|
+
PhotoType = GraphQL::GraphQLObjectType.new do
|
50
|
+
name 'Photo'
|
51
|
+
|
52
|
+
field :id, ! GraphQL::GraphQLID
|
53
|
+
field :width, GraphQL::GraphQLInt
|
54
|
+
|
55
|
+
interface definitions[:interface]
|
56
|
+
end
|
57
|
+
|
58
|
+
QueryType = GraphQL::GraphQLObjectType.new do
|
59
|
+
name 'Query'
|
60
|
+
|
61
|
+
field definitions[:field]
|
62
|
+
end
|
63
|
+
|
64
|
+
Schema = GraphQL::GraphQLSchema.new do
|
65
|
+
query QueryType
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Relay
|
2
|
+
module Node
|
3
|
+
module DataGlobal
|
4
|
+
|
5
|
+
UserStruct = Struct.new('UserGlobal', :id, :name)
|
6
|
+
|
7
|
+
Users = {
|
8
|
+
'1' => UserStruct.new('1', 'John Doe'),
|
9
|
+
'2' => UserStruct.new('2', 'Jane Smith')
|
10
|
+
}
|
11
|
+
|
12
|
+
PhotoStruct = Struct.new('PhotoGlobal', :photo_id, :width)
|
13
|
+
|
14
|
+
Photos = {
|
15
|
+
'3' => PhotoStruct.new('3', 300),
|
16
|
+
'4' => PhotoStruct.new('4', 400)
|
17
|
+
}
|
18
|
+
|
19
|
+
fetcher = lambda { |id, *args|
|
20
|
+
type, id = Relay::Node.from_global_id(id)
|
21
|
+
|
22
|
+
case type
|
23
|
+
when 'User'
|
24
|
+
Users[id]
|
25
|
+
when 'Photo'
|
26
|
+
Photos[id]
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
resolver = lambda { |root|
|
31
|
+
case root
|
32
|
+
when UserStruct
|
33
|
+
UserType
|
34
|
+
when PhotoStruct
|
35
|
+
PhotoType
|
36
|
+
end
|
37
|
+
}
|
38
|
+
|
39
|
+
definitions = Relay::Node.definitions(fetcher, resolver)
|
40
|
+
|
41
|
+
UserType = GraphQL::GraphQLObjectType.new do
|
42
|
+
name 'User'
|
43
|
+
|
44
|
+
global_id_field :id, type_name: 'User'
|
45
|
+
|
46
|
+
field :name, GraphQL::GraphQLString
|
47
|
+
|
48
|
+
interface definitions[:interface]
|
49
|
+
end
|
50
|
+
|
51
|
+
PhotoType = GraphQL::GraphQLObjectType.new do
|
52
|
+
name 'Photo'
|
53
|
+
|
54
|
+
global_id_field :id, type_name: 'Photo', resolve_id: -> (object) { object.photo_id }
|
55
|
+
|
56
|
+
field :width, GraphQL::GraphQLInt
|
57
|
+
|
58
|
+
interface definitions[:interface]
|
59
|
+
end
|
60
|
+
|
61
|
+
QueryType = GraphQL::GraphQLObjectType.new do
|
62
|
+
name 'Query'
|
63
|
+
|
64
|
+
field definitions[:field]
|
65
|
+
|
66
|
+
field :all, + definitions[:interface] do
|
67
|
+
resolve lambda { |*args|
|
68
|
+
Users.values.concat(Photos.values)
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
Schema = GraphQL::GraphQLSchema.new do
|
74
|
+
query QueryType
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'relay'
|
2
|
+
require_relative 'data_global'
|
3
|
+
|
4
|
+
RSpec.describe 'Relay Node with Global ID' do
|
5
|
+
|
6
|
+
def schema
|
7
|
+
Relay::Node::DataGlobal::Schema
|
8
|
+
end
|
9
|
+
|
10
|
+
def all_q1
|
11
|
+
%Q{{
|
12
|
+
all {
|
13
|
+
id
|
14
|
+
}
|
15
|
+
}}
|
16
|
+
end
|
17
|
+
|
18
|
+
def all_q2
|
19
|
+
%Q{{
|
20
|
+
user: node(id: "VXNlcjox") {
|
21
|
+
id
|
22
|
+
... on User {
|
23
|
+
name
|
24
|
+
}
|
25
|
+
}
|
26
|
+
photo: node(id: "UGhvdG86Mw==") {
|
27
|
+
id
|
28
|
+
... on Photo {
|
29
|
+
width
|
30
|
+
}
|
31
|
+
}
|
32
|
+
}}
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
it "Should give different ids" do
|
37
|
+
expectations = {all: [{id: 'VXNlcjox'}, {id: 'VXNlcjoy'}, {id: 'UGhvdG86Mw=='}, {id: 'UGhvdG86NA=='}]}
|
38
|
+
document = GraphQL::Language.parse(all_q1)
|
39
|
+
executor = GraphQL::Executor.new(document, schema)
|
40
|
+
expect(executor.execute({})).to eq(expectations)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "Should refetch ids" do
|
44
|
+
expectations = {user: {id: 'VXNlcjox', name: 'John Doe'}, photo: {id: 'UGhvdG86Mw==', width: 300}}
|
45
|
+
document = GraphQL::Language.parse(all_q2)
|
46
|
+
executor = GraphQL::Executor.new(document, schema)
|
47
|
+
expect(executor.execute({})).to eq(expectations)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'relay'
|
2
|
+
|
3
|
+
RSpec.describe 'Relay Node Plural' do
|
4
|
+
|
5
|
+
UserType = GraphQL::GraphQLObjectType.new do
|
6
|
+
name 'User'
|
7
|
+
|
8
|
+
field :username, GraphQL::GraphQLString
|
9
|
+
field :url, GraphQL::GraphQLString
|
10
|
+
end
|
11
|
+
|
12
|
+
UserStruct = Struct.new('PluralUser', :username, :url)
|
13
|
+
|
14
|
+
QueryType = GraphQL::GraphQLObjectType.new do
|
15
|
+
name 'Query'
|
16
|
+
|
17
|
+
plural_identifying_root_field :usernames do
|
18
|
+
argument_name :usernames
|
19
|
+
input_type GraphQL::GraphQLString
|
20
|
+
output_type UserType
|
21
|
+
|
22
|
+
resolve_single_input lambda { |username, *args|
|
23
|
+
UserStruct.new(username, "www.facebook.com/#{username}")
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Schema = GraphQL::GraphQLSchema.new do
|
29
|
+
|
30
|
+
query QueryType
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def q1
|
35
|
+
%Q(
|
36
|
+
{
|
37
|
+
usernames(usernames: ["dschafer", "leebyron", "schrockn"]) {
|
38
|
+
username
|
39
|
+
url
|
40
|
+
}
|
41
|
+
}
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "Should allow fetching" do
|
46
|
+
document = GraphQL::Language.parse(q1)
|
47
|
+
executor = GraphQL::Executor.new(document, Schema)
|
48
|
+
puts executor.execute({})
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|