graphql-relay 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -0
- data/lib/graphql/relay.rb +7 -1
- data/lib/graphql/relay/connection_field.rb +0 -3
- data/lib/graphql/relay/define.rb +20 -0
- data/lib/graphql/relay/global_node_identification.rb +42 -15
- data/lib/graphql/relay/mutation.rb +17 -3
- data/lib/graphql/relay/version.rb +1 -1
- data/spec/graphql/relay/global_node_identification_spec.rb +35 -0
- data/spec/support/star_wars_data.rb +2 -1
- data/spec/support/star_wars_schema.rb +11 -1
- metadata +7 -7
- data/lib/graphql/relay/monkey_patches/definition_config.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e48e815c037e952c8d1e2a9f54379b5bfbce7ae8
|
4
|
+
data.tar.gz: 629bdf27a10511bdee0664304443b33f999581c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b4a352c2405b890bc4a9b727f80f65f7f3e9c332162848b7fb41560147b4cf57597da9cc1739e47d15441f59528aa20ddba763201c5b58ffd50e1a1cedfaf71
|
7
|
+
data.tar.gz: d630722d99ee1c605bf55df1554d23912ac3f3bb545ef89dd9a24bbd1bf267af25a6838dbf5e22a8d606d764422271aae2a931c3f7beec7050e78ae63d687b84
|
data/README.md
CHANGED
@@ -87,6 +87,32 @@ QueryType = GraphQL::ObjectType.define do
|
|
87
87
|
end
|
88
88
|
```
|
89
89
|
|
90
|
+
#### Custom UUID Generation
|
91
|
+
|
92
|
+
By default, `graphql-relay` uses `Base64.strict_encode64` to generate opaque global ids. You can modify this behavior by providing two configurations. They work together to encode and decode ids:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
|
96
|
+
# ...
|
97
|
+
|
98
|
+
# Return a string for re-fetching this object
|
99
|
+
to_global_id -> (type_name, id) {
|
100
|
+
"#{type_name.downcase}/#{id}"
|
101
|
+
}
|
102
|
+
|
103
|
+
# Based on the incoming string, extract the type_name and id
|
104
|
+
from_global_id -> (global_id) {
|
105
|
+
id_parts = global_id.split("/")
|
106
|
+
type_name = id_parts[0]
|
107
|
+
id = id_parts[1]
|
108
|
+
# Return *both*:
|
109
|
+
type_name, id
|
110
|
+
}
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
`graphql-relay` will use those procs for interacting with global ids.
|
115
|
+
|
90
116
|
### Connections
|
91
117
|
|
92
118
|
Connections provide pagination and `pageInfo` for `Array`s or `ActiveRecord::Relation`s.
|
@@ -318,6 +344,11 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
|
|
318
344
|
|
319
345
|
## Todo
|
320
346
|
|
347
|
+
- Allow custom defined ID scheme
|
348
|
+
- Allow custom edge fields (per connection type)
|
349
|
+
- `GlobalNodeIdentification.to_global_id` should receive the type name and _object_, not `id`. (Or, maintain the "`type_name, id` in, `type_name, id` out" pattern?)
|
350
|
+
- Make GlobalId a property of the schema, not a global
|
351
|
+
|
321
352
|
## More Resources
|
322
353
|
|
323
354
|
- [GraphQL Slack](http://graphql-slack.herokuapp.com), come join us in the `#ruby` channel!
|
data/lib/graphql/relay.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'base64'
|
2
2
|
require 'graphql'
|
3
3
|
# MONKEY PATCHES 😬
|
4
|
-
require 'graphql/relay/monkey_patches/definition_config'
|
5
4
|
require 'graphql/relay/monkey_patches/base_type'
|
6
5
|
|
6
|
+
require 'graphql/relay/define'
|
7
7
|
require 'graphql/relay/global_node_identification'
|
8
8
|
require 'graphql/relay/page_info'
|
9
9
|
require 'graphql/relay/edge'
|
@@ -13,3 +13,9 @@ require 'graphql/relay/relation_connection'
|
|
13
13
|
require 'graphql/relay/global_id_field'
|
14
14
|
require 'graphql/relay/mutation'
|
15
15
|
require 'graphql/relay/connection_field'
|
16
|
+
|
17
|
+
# Accept Relay-specific definitions
|
18
|
+
GraphQL::BaseType.accepts_definitions(
|
19
|
+
connection: GraphQL::Relay::Define::AssignConnection,
|
20
|
+
global_id_field: GraphQL::Relay::Define::AssignGlobalIdField,
|
21
|
+
)
|
@@ -45,9 +45,6 @@ module GraphQL
|
|
45
45
|
def self.get_connection_resolve(field_name, underlying_resolve, max_page_size: nil)
|
46
46
|
-> (obj, args, ctx) {
|
47
47
|
items = underlying_resolve.call(obj, args, ctx)
|
48
|
-
if items == GraphQL::Query::DEFAULT_RESOLVE
|
49
|
-
items = obj.public_send(field_name)
|
50
|
-
end
|
51
48
|
connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
|
52
49
|
connection_class.new(items, args, max_page_size: max_page_size)
|
53
50
|
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module Relay
|
3
|
+
module Define
|
4
|
+
module AssignConnection
|
5
|
+
def self.call(type_defn, name, type = nil, desc = nil, property: nil, max_page_size: nil, &block)
|
6
|
+
underlying_field = GraphQL::Define::AssignObjectField.call(type_defn, name, type, desc, property: property, &block)
|
7
|
+
connection_field = GraphQL::Relay::ConnectionField.create(underlying_field, max_page_size: max_page_size)
|
8
|
+
type_defn.fields[name.to_s] = connection_field
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module AssignGlobalIdField
|
13
|
+
def self.call(type_defn, field_name)
|
14
|
+
type_defn.name || raise("You must define the type's name before creating a GlobalIdField")
|
15
|
+
GraphQL::Define::AssignObjectField.call(type_defn, field_name, field: GraphQL::Relay::GlobalIdField.new(type_defn.name))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -3,20 +3,16 @@ module GraphQL
|
|
3
3
|
module Relay
|
4
4
|
# This object provides helpers for working with global IDs.
|
5
5
|
# It's assumed you'll only have 1!
|
6
|
+
#
|
6
7
|
# GlobalIdField depends on that, since it calls class methods
|
7
8
|
# which delegate to the singleton instance.
|
9
|
+
#
|
8
10
|
class GlobalNodeIdentification
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
self.id_separator = "-"
|
13
|
-
|
14
|
-
include GraphQL::DefinitionHelpers::DefinedByConfig
|
15
|
-
defined_by_config :object_from_id_proc, :type_from_object_proc
|
16
|
-
attr_accessor :object_from_id_proc, :type_from_object_proc
|
11
|
+
include GraphQL::Define::InstanceDefinable
|
12
|
+
accepts_definitions(:object_from_id, :type_from_object, :to_global_id, :from_global_id)
|
17
13
|
|
18
14
|
class << self
|
19
|
-
attr_accessor :instance
|
15
|
+
attr_accessor :instance, :id_separator
|
20
16
|
def new(*args, &block)
|
21
17
|
@instance = super
|
22
18
|
end
|
@@ -30,6 +26,13 @@ module GraphQL
|
|
30
26
|
end
|
31
27
|
end
|
32
28
|
|
29
|
+
self.id_separator = "-"
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@to_global_id_proc = DEFAULT_TO_GLOBAL_ID
|
33
|
+
@from_global_id_proc = DEFAULT_FROM_GLOBAL_ID
|
34
|
+
end
|
35
|
+
|
33
36
|
# Returns `NodeInterface`, which all Relay types must implement
|
34
37
|
def interface
|
35
38
|
@interface ||= begin
|
@@ -56,20 +59,36 @@ module GraphQL
|
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
62
|
+
DEFAULT_TO_GLOBAL_ID = -> (type_name, id) {
|
63
|
+
id_str = id.to_s
|
64
|
+
if type_name.include?(self.id_separator) || id_str.include?(self.id_separator)
|
65
|
+
raise "to_global_id(#{type_name}, #{id}) contains reserved characters `#{self.id_separator}`"
|
66
|
+
end
|
67
|
+
Base64.strict_encode64([type_name, id_str].join(self.id_separator))
|
68
|
+
}
|
69
|
+
|
70
|
+
DEFAULT_FROM_GLOBAL_ID = -> (global_id) {
|
71
|
+
Base64.decode64(global_id).split(self.id_separator)
|
72
|
+
}
|
73
|
+
|
59
74
|
# Create a global ID for type-name & ID
|
60
75
|
# (This is an opaque transform)
|
61
76
|
def to_global_id(type_name, id)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
77
|
+
@to_global_id_proc.call(type_name, id)
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_global_id=(proc)
|
81
|
+
@to_global_id_proc = proc
|
67
82
|
end
|
68
83
|
|
69
84
|
# Get type-name & ID from global ID
|
70
85
|
# (This reverts the opaque transform)
|
71
86
|
def from_global_id(global_id)
|
72
|
-
|
87
|
+
@from_global_id_proc.call(global_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
def from_global_id=(proc)
|
91
|
+
@from_global_id_proc = proc
|
73
92
|
end
|
74
93
|
|
75
94
|
# Use the provided config to
|
@@ -84,11 +103,19 @@ module GraphQL
|
|
84
103
|
end
|
85
104
|
end
|
86
105
|
|
106
|
+
def type_from_object=(proc)
|
107
|
+
@type_from_object_proc = proc
|
108
|
+
end
|
109
|
+
|
87
110
|
# Use the provided config to
|
88
111
|
# get an object from a UUID
|
89
112
|
def object_from_id(id, ctx)
|
90
113
|
@object_from_id_proc.call(id, ctx)
|
91
114
|
end
|
115
|
+
|
116
|
+
def object_from_id=(proc)
|
117
|
+
@object_from_id_proc = proc
|
118
|
+
end
|
92
119
|
end
|
93
120
|
end
|
94
121
|
end
|
@@ -48,9 +48,23 @@ module GraphQL
|
|
48
48
|
# # }}
|
49
49
|
#
|
50
50
|
class Mutation
|
51
|
-
include GraphQL::
|
52
|
-
|
53
|
-
|
51
|
+
include GraphQL::Define::InstanceDefinable
|
52
|
+
accepts_definitions(
|
53
|
+
:name, :description, :resolve,
|
54
|
+
input_field: GraphQL::Define::AssignArgument,
|
55
|
+
return_field: GraphQL::Define::AssignObjectField,
|
56
|
+
)
|
57
|
+
attr_accessor :name, :definition
|
58
|
+
attr_reader :fields, :arguments
|
59
|
+
|
60
|
+
# For backwards compat, but do we need this separate API?
|
61
|
+
alias :return_fields :fields
|
62
|
+
alias :input_fields :arguments
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
@fields = {}
|
66
|
+
@arguments = {}
|
67
|
+
end
|
54
68
|
|
55
69
|
def resolve=(proc)
|
56
70
|
@resolve_proc = proc
|
@@ -75,6 +75,41 @@ describe GraphQL::Relay::GlobalNodeIdentification do
|
|
75
75
|
}
|
76
76
|
assert_includes err.message, "to_global_id(Best-Thing, 234) contains reserved characters `-`"
|
77
77
|
end
|
78
|
+
|
79
|
+
describe "custom definitions" do
|
80
|
+
before do
|
81
|
+
@previous_global_node_id = GraphQL::Relay::GlobalNodeIdentification.instance
|
82
|
+
@new_node_id = GraphQL::Relay::GlobalNodeIdentification.define do
|
83
|
+
to_global_id -> (type_name, id) {
|
84
|
+
"#{type_name}/#{id}"
|
85
|
+
}
|
86
|
+
|
87
|
+
from_global_id -> (global_id) {
|
88
|
+
global_id.split("/")
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
after do
|
94
|
+
GraphQL::Relay::GlobalNodeIdentification.instance = @previous_global_node_id
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "generating IDs" do
|
98
|
+
it "Applies custom-defined ID generation" do
|
99
|
+
result = query(%| { largestBase { id } }|)
|
100
|
+
generated_id = result["data"]["largestBase"]["id"]
|
101
|
+
assert_equal "Base/13", generated_id
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "fetching by ID" do
|
106
|
+
it "Deconstructs the ID by the custom proc" do
|
107
|
+
result = query(%| { node(id: "Base/11") { ... on Base { name } } }|)
|
108
|
+
base_name = result["data"]["node"]["name"]
|
109
|
+
assert_equal "Yavin", base_name
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
78
113
|
end
|
79
114
|
|
80
115
|
describe "type_from_object" do
|
@@ -8,13 +8,19 @@
|
|
8
8
|
# See global_node_identification.rb for the full API.
|
9
9
|
NodeIdentification = GraphQL::Relay::GlobalNodeIdentification.define do
|
10
10
|
object_from_id -> (id, ctx) do
|
11
|
-
|
11
|
+
# In a normal app, you could call `from_global_id` on your defined object
|
12
|
+
# type_name, id = NodeIdentification.from_global_id(id)
|
13
|
+
#
|
14
|
+
# But to support our testing setup, reach for the global:
|
15
|
+
type_name, id = GraphQL::Relay::GlobalNodeIdentification.from_global_id(id)
|
12
16
|
STAR_WARS_DATA[type_name][id]
|
13
17
|
end
|
14
18
|
|
15
19
|
type_from_object -> (object) do
|
16
20
|
if object == :test_error
|
17
21
|
:not_a_type
|
22
|
+
elsif object.is_a?(Base)
|
23
|
+
BaseType
|
18
24
|
else
|
19
25
|
STAR_WARS_DATA["Faction"].values.include?(object) ? Faction : Ship
|
20
26
|
end
|
@@ -133,6 +139,10 @@ QueryType = GraphQL::ObjectType.define do
|
|
133
139
|
resolve -> (obj, args, ctx) { STAR_WARS_DATA["Faction"]["2"]}
|
134
140
|
end
|
135
141
|
|
142
|
+
field :largestBase, BaseType do
|
143
|
+
resolve -> (obj, args, ctx) { Base.find(13) }
|
144
|
+
end
|
145
|
+
|
136
146
|
field :node, field: NodeIdentification.field
|
137
147
|
end
|
138
148
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql-relay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Mosolgo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.12'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.12'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,14 +170,14 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: '
|
173
|
+
version: '11.0'
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: '
|
180
|
+
version: '11.0'
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: sqlite3
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -205,11 +205,11 @@ files:
|
|
205
205
|
- lib/graphql/relay/array_connection.rb
|
206
206
|
- lib/graphql/relay/base_connection.rb
|
207
207
|
- lib/graphql/relay/connection_field.rb
|
208
|
+
- lib/graphql/relay/define.rb
|
208
209
|
- lib/graphql/relay/edge.rb
|
209
210
|
- lib/graphql/relay/global_id_field.rb
|
210
211
|
- lib/graphql/relay/global_node_identification.rb
|
211
212
|
- lib/graphql/relay/monkey_patches/base_type.rb
|
212
|
-
- lib/graphql/relay/monkey_patches/definition_config.rb
|
213
213
|
- lib/graphql/relay/mutation.rb
|
214
214
|
- lib/graphql/relay/page_info.rb
|
215
215
|
- lib/graphql/relay/relation_connection.rb
|
@@ -1,26 +0,0 @@
|
|
1
|
-
class GraphQL::DefinitionHelpers::DefinedByConfig::DefinitionConfig
|
2
|
-
# Wraps a field definition with a ConnectionField
|
3
|
-
def connection(name, type = nil, desc = nil, property: nil, max_page_size: nil, &block)
|
4
|
-
underlying_field = field(name, type, desc, property: property, &block)
|
5
|
-
connection_field = GraphQL::Relay::ConnectionField.create(underlying_field, max_page_size: max_page_size)
|
6
|
-
fields[name.to_s] = connection_field
|
7
|
-
end
|
8
|
-
|
9
|
-
alias :return_field :field
|
10
|
-
alias :return_fields :fields
|
11
|
-
|
12
|
-
def global_id_field(field_name)
|
13
|
-
name || raise("You must define the type's name before creating a GlobalIdField")
|
14
|
-
field(field_name, field: GraphQL::Relay::GlobalIdField.new(name))
|
15
|
-
end
|
16
|
-
|
17
|
-
# Support GlobalNodeIdentification
|
18
|
-
attr_accessor :object_from_id_proc, :type_from_object_proc
|
19
|
-
def object_from_id(proc)
|
20
|
-
@object_from_id_proc = proc
|
21
|
-
end
|
22
|
-
|
23
|
-
def type_from_object(proc)
|
24
|
-
@type_from_object_proc = proc
|
25
|
-
end
|
26
|
-
end
|