graphql-relay 0.2.0 → 0.3.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 +7 -11
- data/lib/graphql/definition_helpers/defined_by_config/definition_config.rb +27 -2
- data/lib/graphql/object_type.rb +7 -0
- data/lib/graphql/relay.rb +3 -2
- data/lib/graphql/relay/array_connection.rb +1 -0
- data/lib/graphql/relay/base_connection.rb +23 -3
- data/lib/graphql/relay/relation_connection.rb +3 -0
- data/lib/graphql/relay/version.rb +1 -1
- data/spec/graphql/relay/array_connection_spec.rb +18 -3
- data/spec/graphql/relay/relation_connection_spec.rb +10 -6
- data/spec/support/star_wars_schema.rb +10 -13
- metadata +3 -4
- data/lib/graphql/relay/connection_field.rb +0 -23
- data/lib/graphql/relay/connection_type.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a86533b3e1bc7030fe0016ee41e9492e25cc597
|
4
|
+
data.tar.gz: d80d3d7006d8f37a7f208b49c2fd3dc5f78081c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c0f624fba3f5e81724bdf73459cf72747f8f6f8ca9a569e15b99cd4391a72d25d098ec6f1c3932ce794d4d0db33b1ea2ec0e2292d11e7b79070989de2a0da09
|
7
|
+
data.tar.gz: 04e49ec547a0930ac9b26a54b5a7107dd5a61fe1f758449915c58b4ea7f4c2bbc4917a49e77fff80f0497a478b83624e1b97461dffc21694fec20d8fa6f55e03
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# graphql-relay
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/graphql-relay.svg)](http://badge.fury.io/rb/graphql-relay)
|
4
|
+
[![Build Status](https://travis-ci.org/rmosolgo/graphql-relay-ruby.svg?branch=master)](https://travis-ci.org/rmosolgo/graphql-relay-ruby)
|
4
5
|
[![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby)
|
5
6
|
[![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/coverage)
|
6
7
|
|
@@ -24,24 +25,19 @@ Global Ids provide refetching & global identification for Relay.
|
|
24
25
|
|
25
26
|
You should implement an object that responds to `#object_from_id(global_id)` & `#type_from_object(object)`, then pass it to `GraphQL::Relay::Node.create(implementation)`. [Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/120b750cf86f1eb5c9997b588f022b2ef3a0012c/spec/support/star_wars_schema.rb#L4-L15)
|
26
27
|
|
27
|
-
Then, you can add global id fields to your types with `
|
28
|
+
Then, you can add global id fields to your types with `global_id_field` definition helper.
|
29
|
+
[Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L29)
|
28
30
|
|
29
31
|
### Connections
|
30
32
|
|
31
|
-
Connections will provide arguments, pagination and `pageInfo` for `Array`s or `ActiveRecord::Relation`s.
|
32
|
-
|
33
|
-
To create a connection, you should:
|
34
|
-
- create a connection type; then
|
35
|
-
- implement the field to return objects
|
36
|
-
|
37
|
-
To create a connection type, use either `GraphQL::Relay::ArrayConnection.create_type(base_type)` or for an `ActiveRecord::Relation`, use `GraphQL::Relay::RelationConnection.create_type(base_type)`. [Array example](https://github.com/rmosolgo/graphql-relay-ruby/blob/120b750cf86f1eb5c9997b588f022b2ef3a0012c/spec/support/star_wars_schema.rb#L27), [Relation example](https://github.com/rmosolgo/graphql-relay-ruby/blob/120b750cf86f1eb5c9997b588f022b2ef3a0012c/spec/support/star_wars_schema.rb#L39)
|
33
|
+
Connections will provide arguments, pagination and `pageInfo` for `Array`s or `ActiveRecord::Relation`s. You can use the `connection` definition helper.
|
38
34
|
|
39
35
|
Then, implement the field. It's different than a normal field:
|
40
36
|
- use the `connection` helper to define it, instead of `field`
|
41
|
-
-
|
37
|
+
- Call `#connection_type` on an `ObjectType` for the field's return type (eg, `ShipType.connection_type`)
|
42
38
|
- implement `resolve` to return an Array or an ActiveRecord::Relation, depending on the connection type.
|
43
39
|
|
44
|
-
[Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/
|
40
|
+
[Example 1](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L39-L51), [Example 2](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L52-L58)
|
45
41
|
|
46
42
|
### Mutations
|
47
43
|
|
@@ -69,7 +65,7 @@ Examples:
|
|
69
65
|
|
70
66
|
## Todo
|
71
67
|
|
72
|
-
- [ ]
|
68
|
+
- [ ] Fix `Node.create` -- make it return one object which exposes useful info
|
73
69
|
|
74
70
|
## More Resources
|
75
71
|
|
@@ -3,14 +3,39 @@ module GraphQL
|
|
3
3
|
module DefinedByConfig
|
4
4
|
class DefinitionConfig
|
5
5
|
# Wraps a field definition with a ConnectionField
|
6
|
+
# - applies default fields
|
7
|
+
# - wraps the resolve proc to make a connection
|
8
|
+
#
|
6
9
|
def connection(name, type = nil, desc = nil, property: nil, &block)
|
7
|
-
|
8
|
-
|
10
|
+
# Wrap the given block to define the default args
|
11
|
+
definition_block = -> (config) {
|
12
|
+
argument :first, types.Int
|
13
|
+
argument :after, types.String
|
14
|
+
argument :last, types.Int
|
15
|
+
argument :before, types.String
|
16
|
+
argument :order, types.String
|
17
|
+
self.instance_eval(&block)
|
18
|
+
}
|
19
|
+
connection_field = field(name, type, desc, property: property, &definition_block)
|
20
|
+
# Wrap the defined resolve proc
|
21
|
+
# TODO: make a public API on GraphQL::Field to expose this proc
|
22
|
+
original_resolve = connection_field.instance_variable_get(:@resolve_proc)
|
23
|
+
connection_resolve = -> (obj, args, ctx) {
|
24
|
+
items = original_resolve.call(obj, args, ctx)
|
25
|
+
connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
|
26
|
+
connection_class.new(items, args)
|
27
|
+
}
|
28
|
+
connection_field.resolve = connection_resolve
|
9
29
|
fields[name.to_s] = connection_field
|
10
30
|
end
|
11
31
|
|
12
32
|
alias :return_field :field
|
13
33
|
alias :return_fields :fields
|
34
|
+
|
35
|
+
def global_id_field(field_name)
|
36
|
+
name || raise("You must define the type's name before creating a GlobalIdField")
|
37
|
+
field(name, field: GraphQL::Relay::GlobalIdField.new(name))
|
38
|
+
end
|
14
39
|
end
|
15
40
|
end
|
16
41
|
end
|
data/lib/graphql/relay.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'base64'
|
2
|
+
# MONKEY PATCHES 😬
|
2
3
|
require 'graphql/definition_helpers/defined_by_config/definition_config'
|
4
|
+
require_relative './object_type.rb'
|
5
|
+
|
3
6
|
require 'graphql/relay/node'
|
4
7
|
require 'graphql/relay/page_info'
|
5
8
|
require 'graphql/relay/edge'
|
6
|
-
require 'graphql/relay/connection_type'
|
7
9
|
require 'graphql/relay/base_connection'
|
8
10
|
require 'graphql/relay/array_connection'
|
9
11
|
require 'graphql/relay/relation_connection'
|
10
12
|
require 'graphql/relay/global_id_field'
|
11
|
-
require 'graphql/relay/connection_field'
|
12
13
|
require 'graphql/relay/mutation'
|
@@ -13,21 +13,41 @@ module GraphQL
|
|
13
13
|
# Just to encode data in the cursor, use something that won't conflict
|
14
14
|
CURSOR_SEPARATOR = "---"
|
15
15
|
|
16
|
+
# Map of collection classes -> connection_classes
|
17
|
+
# eg Array -> ArrayConnection
|
18
|
+
CONNECTION_IMPLEMENTATIONS = {}
|
19
|
+
|
16
20
|
# Create a connection which exposes edges of this type
|
17
21
|
def self.create_type(wrapped_type)
|
18
22
|
edge_type = Edge.create_type(wrapped_type)
|
19
23
|
|
20
|
-
connection_type =
|
24
|
+
connection_type = ObjectType.define do
|
21
25
|
name("#{wrapped_type.name}Connection")
|
22
26
|
field :edges, types[edge_type]
|
23
27
|
field :pageInfo, PageInfo, property: :page_info
|
24
28
|
end
|
25
29
|
|
26
|
-
connection_type.connection_class = self
|
27
|
-
|
28
30
|
connection_type
|
29
31
|
end
|
30
32
|
|
33
|
+
# @return [subclass of BaseConnection] a connection wrapping `items`
|
34
|
+
def self.connection_for_items(items)
|
35
|
+
implementation = CONNECTION_IMPLEMENTATIONS.find do |items_class, connection_class|
|
36
|
+
items.is_a?(items_class)
|
37
|
+
end
|
38
|
+
if implementation.nil?
|
39
|
+
raise("No connection implementation to wrap #{items.class}")
|
40
|
+
else
|
41
|
+
implementation[1]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add `connection_class` as the connection wrapper for `items_class`
|
46
|
+
# eg, `RelationConnection` is the implementation for `AR::Relation`
|
47
|
+
def self.register_connection_implementation(items_class, connection_class)
|
48
|
+
CONNECTION_IMPLEMENTATIONS[items_class] = connection_class
|
49
|
+
end
|
50
|
+
|
31
51
|
attr_reader :object, :arguments
|
32
52
|
|
33
53
|
def initialize(object, arguments)
|
@@ -5,11 +5,15 @@ describe GraphQL::Relay::ArrayConnection do
|
|
5
5
|
ships = result["data"]["rebels"]["ships"]["edges"]
|
6
6
|
names = ships.map { |e| e["node"]["name"] }
|
7
7
|
end
|
8
|
+
|
9
|
+
def get_last_cursor(result)
|
10
|
+
result["data"]["rebels"]["ships"]["edges"].last["cursor"]
|
11
|
+
end
|
8
12
|
describe "results" do
|
9
13
|
let(:query_string) {%|
|
10
|
-
query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String){
|
14
|
+
query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String, $nameIncludes: String){
|
11
15
|
rebels {
|
12
|
-
ships(first: $first, after: $after, last: $last, before: $before, order: $order) {
|
16
|
+
ships(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
|
13
17
|
edges {
|
14
18
|
cursor
|
15
19
|
node {
|
@@ -47,7 +51,7 @@ describe GraphQL::Relay::ArrayConnection do
|
|
47
51
|
assert_equal(["X-Wing", "Y-Wing", "A-Wing"], get_names(result))
|
48
52
|
|
49
53
|
# After the last result, find the next 2:
|
50
|
-
last_cursor = result
|
54
|
+
last_cursor = get_last_cursor(result)
|
51
55
|
|
52
56
|
result = query(query_string, "after" => last_cursor, "first" => 2)
|
53
57
|
assert_equal(["Millenium Falcon", "Home One"], get_names(result))
|
@@ -71,5 +75,16 @@ describe GraphQL::Relay::ArrayConnection do
|
|
71
75
|
result = query(query_string, "first" => 2, "order" => "-name")
|
72
76
|
assert_equal(["Y-Wing", "X-Wing"], get_names(result))
|
73
77
|
end
|
78
|
+
|
79
|
+
it 'applies custom arguments' do
|
80
|
+
result = query(query_string, "nameIncludes" => "Wing", "first" => 2)
|
81
|
+
names = get_names(result)
|
82
|
+
assert_equal(2, names.length)
|
83
|
+
|
84
|
+
after = get_last_cursor(result)
|
85
|
+
result = query(query_string, "nameIncludes" => "Wing", "after" => after)
|
86
|
+
names = get_names(result)
|
87
|
+
assert_equal(1, names.length)
|
88
|
+
end
|
74
89
|
end
|
75
90
|
end
|
@@ -15,18 +15,22 @@ describe GraphQL::Relay::RelationConnection do
|
|
15
15
|
query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String){
|
16
16
|
empire {
|
17
17
|
bases(first: $first, after: $after, last: $last, before: $before, order: $order) {
|
18
|
-
|
19
|
-
cursor
|
20
|
-
node {
|
21
|
-
name
|
22
|
-
}
|
23
|
-
}
|
18
|
+
... basesConnection
|
24
19
|
pageInfo {
|
25
20
|
hasNextPage
|
26
21
|
}
|
27
22
|
}
|
28
23
|
}
|
29
24
|
}
|
25
|
+
|
26
|
+
fragment basesConnection on BaseConnection {
|
27
|
+
edges {
|
28
|
+
cursor
|
29
|
+
node {
|
30
|
+
name
|
31
|
+
}
|
32
|
+
}
|
33
|
+
}
|
30
34
|
|}
|
31
35
|
it 'limits the result' do
|
32
36
|
result = query(query_string, "first" => 2)
|
@@ -23,36 +23,33 @@ Ship = GraphQL::ObjectType.define do
|
|
23
23
|
field :name, types.String
|
24
24
|
end
|
25
25
|
|
26
|
-
# Define a connection which will wrap an array:
|
27
|
-
ShipConnection = GraphQL::Relay::ArrayConnection.create_type(Ship)
|
28
|
-
|
29
|
-
|
30
26
|
BaseType = GraphQL::ObjectType.define do
|
31
27
|
name "Base"
|
32
28
|
interfaces [NodeInterface]
|
33
|
-
|
29
|
+
global_id_field :id
|
34
30
|
field :name, types.String
|
35
31
|
field :planet, types.String
|
36
32
|
end
|
37
33
|
|
38
|
-
# Define a connection which will wrap an ActiveRecord::Relation:
|
39
|
-
BaseConnection = GraphQL::Relay::RelationConnection.create_type(BaseType)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
34
|
Faction = GraphQL::ObjectType.define do
|
44
35
|
name "Faction"
|
45
36
|
interfaces [NodeInterface]
|
46
37
|
field :id, field: GraphQL::Relay::GlobalIdField.new("Faction")
|
47
38
|
field :name, types.String
|
48
|
-
connection :ships,
|
39
|
+
connection :ships, Ship.connection_type do
|
49
40
|
# Resolve field should return an Array, the Connection
|
50
41
|
# will do the rest!
|
51
42
|
resolve -> (obj, args, ctx) {
|
52
|
-
obj.ships.map {|ship_id| STAR_WARS_DATA["Ship"][ship_id] }
|
43
|
+
all_ships = obj.ships.map {|ship_id| STAR_WARS_DATA["Ship"][ship_id] }
|
44
|
+
if args[:nameIncludes]
|
45
|
+
all_ships = all_ships.select { |ship| ship.name.include?(args[:nameIncludes])}
|
46
|
+
end
|
47
|
+
all_ships
|
53
48
|
}
|
49
|
+
# You can define arguments here and use them in the connection
|
50
|
+
argument :nameIncludes, types.String
|
54
51
|
end
|
55
|
-
connection :bases,
|
52
|
+
connection :bases, BaseType.connection_type do
|
56
53
|
# Resolve field should return an Array, the Connection
|
57
54
|
# will do the rest!
|
58
55
|
resolve -> (obj, args, ctx) {
|
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.3.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: 2015-08-
|
11
|
+
date: 2015-08-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: graphql
|
@@ -188,11 +188,10 @@ extra_rdoc_files: []
|
|
188
188
|
files:
|
189
189
|
- README.md
|
190
190
|
- lib/graphql/definition_helpers/defined_by_config/definition_config.rb
|
191
|
+
- lib/graphql/object_type.rb
|
191
192
|
- lib/graphql/relay.rb
|
192
193
|
- lib/graphql/relay/array_connection.rb
|
193
194
|
- lib/graphql/relay/base_connection.rb
|
194
|
-
- lib/graphql/relay/connection_field.rb
|
195
|
-
- lib/graphql/relay/connection_type.rb
|
196
195
|
- lib/graphql/relay/edge.rb
|
197
196
|
- lib/graphql/relay/global_id_field.rb
|
198
197
|
- lib/graphql/relay/mutation.rb
|
@@ -1,23 +0,0 @@
|
|
1
|
-
module GraphQL
|
2
|
-
module Relay
|
3
|
-
# The best way to make these is with the connection helper,
|
4
|
-
# @see {GraphQL::DefinitionHelpers::DefinedByConfig::DefinitionConfig}
|
5
|
-
class ConnectionField
|
6
|
-
def self.create(underlying_field)
|
7
|
-
field = GraphQL::Field.define do
|
8
|
-
argument :first, types.Int
|
9
|
-
argument :after, types.String
|
10
|
-
argument :last, types.Int
|
11
|
-
argument :before, types.String
|
12
|
-
argument :order, types.String
|
13
|
-
|
14
|
-
type(-> { underlying_field.type })
|
15
|
-
resolve -> (obj, args, ctx) {
|
16
|
-
items = underlying_field.resolve(obj, args, ctx)
|
17
|
-
underlying_field.type.connection_class.new(items, args)
|
18
|
-
}
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
module GraphQL
|
2
|
-
module Relay
|
3
|
-
# An ObjectType which also stores its {#connection_class}
|
4
|
-
class ConnectionType < GraphQL::ObjectType
|
5
|
-
defined_by_config :name, :fields, :interfaces
|
6
|
-
# @return [Class] A subclass of {BaseConnection}
|
7
|
-
attr_accessor :connection_class
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|