graphql-relay 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dbc5dd733714ace5095fc3a5f9a7a465a046af0c
4
- data.tar.gz: b609505dea93ea2d6a9e685e3ba379d80d35d286
3
+ metadata.gz: f9e0b75206216efdc87ad4a9d85f6032d89ea4ec
4
+ data.tar.gz: a8ed52c2debcd019a00f62f5ebe0eee3ebe17c63
5
5
  SHA512:
6
- metadata.gz: 2e247f4e0963c80f99bebea7aae3788135cf12b63222478820ad5621d85583c2b5e53e5104c5d33b3be79ca4cbd3b623f98fbbc6ea7f94fd097f79f5d086bdc2
7
- data.tar.gz: 65c4fe9c224c36bf0a687cbadfedbaac750a7178887b7aa86ee69894efc5f48c117ea8efb3eda037ce2a87a1ecf628328b94e05ed11d6e8a854fe60bb6d0fe73
6
+ metadata.gz: 34a25f845c250b77c5f7ecfc31aadbcacf0ac4f4ada1d102cfbeb687d29a4ddd983b0e32cedd20bb87f06d8dceef9099361936c99c56194c9156ce565b8ca674
7
+ data.tar.gz: bbd6a2a4f2def1b7eb981f9190a1291a5a2ba5999e3590f62c4a33c6c928fa1f1c81dd7edf1f9c4fe06540dc00a1a4116b048faad50f663c1c78d7a8da290f8a
data/README.md CHANGED
@@ -5,7 +5,8 @@
5
5
  [![Code Climate](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/badges/gpa.svg)](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby)
6
6
  [![Test Coverage](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/badges/coverage.svg)](https://codeclimate.com/github/rmosolgo/graphql-relay-ruby/coverage)
7
7
 
8
- Helpers for using [`graphql`](https://github.com/rmosolgo/graphql-ruby) with Relay.
8
+ Helpers for using [`graphql`](https://github.com/rmosolgo/graphql-ruby) with Relay. Includes support for serving Relay connections from `Array`s, `ActiveRecord::Relation`s and `Sequel::Dataset`s.
9
+
9
10
 
10
11
  [API Documentation](http://www.rubydoc.info/github/rmosolgo/graphql-relay-ruby)
11
12
  ## Installation
@@ -115,7 +116,7 @@ end
115
116
 
116
117
  ### Connections
117
118
 
118
- Connections provide pagination and `pageInfo` for `Array`s or `ActiveRecord::Relation`s.
119
+ Connections provide pagination and `pageInfo` for `Array`s, `ActiveRecord::Relation`s or `Sequel::Dataset`s.
119
120
 
120
121
  #### Connection fields
121
122
 
@@ -136,6 +137,9 @@ You can also define custom arguments and a custom resolve function for connectio
136
137
 
137
138
  ```ruby
138
139
  connection :featured_comments, CommentType.connection_type do
140
+ # Use a name to disambiguate this from `CommentType.connection_type`
141
+ name "CommentConnectionWithSince"
142
+
139
143
  # Add an argument:
140
144
  argument :since, types.String
141
145
 
@@ -165,16 +169,119 @@ connection :featured_comments, CommentType.connection_type, max_page_size: 50
165
169
  You can customize a connection type with `.define_connection`:
166
170
 
167
171
  ```ruby
168
- PostType.define_connection do
172
+ PostConnectionWithTotalCountType = PostType.define_connection do
169
173
  field :totalCount do
170
174
  type types.Int
171
175
  # `obj` is the Connection, `obj.object` is the collection of Posts
172
176
  resolve -> (obj, args, ctx) { obj.object.count }
173
177
  end
174
178
  end
179
+
180
+ ```
181
+
182
+ Now, you can use `PostConnectionWithTotalCountType` to define a connection with the "totalCount" field:
183
+
184
+ ```ruby
185
+ AuthorType = GraphQL::ObjectType.define do
186
+ # Use the custom connection type:
187
+ connection :posts, PostConnectionWithTotalCountType
188
+ end
189
+ ```
190
+
191
+ #### Custom edge types
192
+
193
+ If you need custom fields on `edge`s, you can define an edge type and pass it to a connection:
194
+
195
+ ```ruby
196
+ # Person => Membership => Team
197
+ MembershipSinceEdgeType = BaseType.define_edge do
198
+ name "MembershipSinceEdge"
199
+ field :memberSince, types.Int, "The date that this person joined this team" do
200
+ resolve -> (obj, args, ctx) {
201
+ obj # => GraphQL::Relay::Edge instnce
202
+ person = obj.parent
203
+ team = obj.node
204
+ membership = Membership.where(person: person, team: team).first
205
+ membership.created_at.to_i
206
+ }
207
+ end
208
+ end
175
209
  ```
176
210
 
177
- Now, `PostType.connection_type` will include a `totalCount` field.
211
+ Then, pass the edge type when defining the connection type:
212
+
213
+ ```ruby
214
+ TeamMembershipsConnectionType = TeamType.define_connection(edge_type: MembershipSinceEdgeType) do
215
+ # Use a name so it doesn't conflict with "TeamConnection"
216
+ name "TeamMembershipsConnection"
217
+ end
218
+ ```
219
+
220
+ Now, you can query custom fields on the `edge`:
221
+
222
+ ```graphql
223
+ {
224
+ me {
225
+ teams {
226
+ edge {
227
+ memberSince # <= Here's your custom field
228
+ node {
229
+ teamName: name
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
+ ```
236
+
237
+ #### Custom Edge classes
238
+
239
+ For more robust custom edges, you can define a custom edge class. It will be `obj` in the edge type's resolve function. For example, to define a membership edge:
240
+
241
+ ```ruby
242
+ # Make sure to familiarize yourself with GraphQL::Relay::Edge --
243
+ # you have to avoid naming conflicts here!
244
+ class MembershipSinceEdge < GraphQL::Relay::Edge
245
+ # Cache `membership` to avoid multiple DB queries
246
+ def membership
247
+ @membership ||= begin
248
+ # "parent" and "node" are passed in from the surrounding Connection,
249
+ # See `Edge#initialize` for details
250
+ person = self.parent
251
+ team = self.node
252
+ Membership.where(person: person, team: team).first
253
+ end
254
+ end
255
+
256
+ def member_since
257
+ membership.created_at.to_i
258
+ end
259
+
260
+ def leader?
261
+ membership.leader?
262
+ end
263
+ end
264
+ ```
265
+
266
+ Then, hook it up with custom edge type and custom connection type:
267
+
268
+ ```ruby
269
+ # Person => Membership => Team
270
+ MembershipSinceEdgeType = BaseType.define_edge do
271
+ name "MembershipSinceEdge"
272
+ field :memberSince, types.Int, "The date that this person joined this team", property: :member_since
273
+ field :isPrimary, types.Boolean, "Is this person the team leader?". property: :primary?
274
+ end
275
+ end
276
+
277
+ TeamMembershipsConnectionType = TeamType.define_connection(
278
+ edge_class: MembershipSinceEdge,
279
+ edge_type: MembershipSinceEdgeType,
280
+ ) do
281
+ # Use a name so it doesn't conflict with "TeamConnection"
282
+ name "TeamMembershipsConnection"
283
+ end
284
+ ```
178
285
 
179
286
  #### Connection objects
180
287
 
@@ -237,7 +344,6 @@ field = GraphQL::Field.new
237
344
  connection_field = GraphQL::Relay::ConnectionField.create(field)
238
345
  ```
239
346
 
240
-
241
347
  ### Mutations
242
348
 
243
349
  Mutations allow Relay to mutate your system. They conform to a strict API which makes them predictable to the client.
@@ -342,7 +448,6 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
342
448
 
343
449
  ## Todo
344
450
 
345
- - Allow custom edge fields (per connection type)
346
451
  - `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?)
347
452
  - Make GlobalId a property of the schema, not a global
348
453
  - Reduce duplication in ArrayConnection / RelationConnection
data/lib/graphql/relay.rb CHANGED
@@ -7,12 +7,14 @@ 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'
10
+ require 'graphql/relay/edge_type'
10
11
  require 'graphql/relay/base_connection'
11
12
  require 'graphql/relay/array_connection'
12
13
  require 'graphql/relay/relation_connection'
13
14
  require 'graphql/relay/global_id_field'
14
15
  require 'graphql/relay/mutation'
15
16
  require 'graphql/relay/connection_field'
17
+ require 'graphql/relay/connection_type'
16
18
 
17
19
  # Accept Relay-specific definitions
18
20
  GraphQL::BaseType.accepts_definitions(
@@ -14,24 +14,10 @@ module GraphQL
14
14
  # Just to encode data in the cursor, use something that won't conflict
15
15
  CURSOR_SEPARATOR = "---"
16
16
 
17
- # Map of collection classes -> connection_classes
18
- # eg Array -> ArrayConnection
17
+ # Map of collection class names -> connection_classes
18
+ # eg {"Array" => ArrayConnection}
19
19
  CONNECTION_IMPLEMENTATIONS = {}
20
20
 
21
- # Create a connection which exposes edges of this type
22
- def self.create_type(wrapped_type, &block)
23
- edge_type = wrapped_type.edge_type
24
-
25
- connection_type = ObjectType.define do
26
- name("#{wrapped_type.name}Connection")
27
- field :edges, types[edge_type]
28
- field :pageInfo, PageInfo, property: :page_info
29
- block && instance_eval(&block)
30
- end
31
-
32
- connection_type
33
- end
34
-
35
21
  # Find a connection implementation suitable for exposing `items`
36
22
  #
37
23
  # @param [Object] A collection of items (eg, Array, AR::Relation)
@@ -61,16 +47,18 @@ module GraphQL
61
47
  CONNECTION_IMPLEMENTATIONS[items_class.name] = connection_class
62
48
  end
63
49
 
64
- attr_reader :object, :arguments, :max_page_size
50
+ attr_reader :object, :arguments, :max_page_size, :parent
65
51
 
66
52
  # Make a connection, wrapping `object`
67
53
  # @param The collection of results
68
54
  # @param Query arguments
69
55
  # @param max_page_size [Int] The maximum number of results to return
70
- def initialize(object, arguments, max_page_size: nil)
56
+ # @param parent [Object] The object which this collection belongs to
57
+ def initialize(object, arguments, max_page_size: nil, parent: nil)
71
58
  @object = object
72
59
  @arguments = arguments
73
60
  @max_page_size = max_page_size
61
+ @parent = parent
74
62
  end
75
63
 
76
64
  # Provide easy access to provided arguments:
@@ -92,9 +80,10 @@ module GraphQL
92
80
  end
93
81
  end
94
82
 
95
- # Wrap nodes in {Edge}s so they expose cursors.
96
- def edges
97
- @edges ||= paged_nodes.map { |item| Edge.new(item, self) }
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
98
87
  end
99
88
 
100
89
  # Support the `pageInfo` field
@@ -0,0 +1,25 @@
1
+ module GraphQL
2
+ module Relay
3
+ module ConnectionType
4
+ # Create a connection which exposes edges of this type
5
+ def self.create_type(wrapped_type, edge_type: nil, edge_class: nil, &block)
6
+ edge_type ||= wrapped_type.edge_type
7
+ edge_class ||= GraphQL::Relay::Edge
8
+ connection_type_name = "#{wrapped_type.name}Connection"
9
+
10
+ connection_type = ObjectType.define do
11
+ name(connection_type_name)
12
+ field :edges, types[edge_type] do
13
+ resolve -> (obj, args, ctx) {
14
+ obj.edge_nodes.map { |item| edge_class.new(item, obj) }
15
+ }
16
+ end
17
+ field :pageInfo, PageInfo, property: :page_info
18
+ block && instance_eval(&block)
19
+ end
20
+
21
+ connection_type
22
+ end
23
+ end
24
+ end
25
+ end
@@ -4,22 +4,19 @@ module GraphQL
4
4
  #
5
5
  # Wraps an object as a `node`, and exposes a connection-specific `cursor`.
6
6
  class Edge < GraphQL::ObjectType
7
- attr_reader :node
7
+ attr_reader :node, :parent, :connection
8
8
  def initialize(node, connection)
9
9
  @node = node
10
10
  @connection = connection
11
+ @parent = parent
11
12
  end
12
13
 
13
14
  def cursor
14
- @cursor ||= @connection.cursor_from_node(node)
15
+ @cursor ||= connection.cursor_from_node(node)
15
16
  end
16
17
 
17
- def self.create_type(wrapped_type)
18
- GraphQL::ObjectType.define do
19
- name("#{wrapped_type.name}Edge")
20
- field :node, wrapped_type
21
- field :cursor, !types.String
22
- end
18
+ def parent
19
+ @parent ||= connection.parent
23
20
  end
24
21
  end
25
22
  end
@@ -0,0 +1,14 @@
1
+ module GraphQL
2
+ module Relay
3
+ module EdgeType
4
+ def self.create_type(wrapped_type, name: nil, &block)
5
+ GraphQL::ObjectType.define do
6
+ name("#{wrapped_type.name}Edge")
7
+ field :node, wrapped_type
8
+ field :cursor, !types.String
9
+ block && instance_eval(&block)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,16 +1,21 @@
1
1
  class GraphQL::BaseType
2
+ # Get the default connection type for this object type
2
3
  def connection_type
3
4
  @connection_type ||= define_connection
4
5
  end
5
6
 
7
+ # Define a custom connection type for this object type
8
+ def define_connection(**kwargs, &block)
9
+ GraphQL::Relay::ConnectionType.create_type(self, **kwargs, &block)
10
+ end
11
+
12
+ # Get the default edge type for this object type
6
13
  def edge_type
7
- @edge_type ||= GraphQL::Relay::Edge.create_type(self)
14
+ @edge_type ||= define_edge
8
15
  end
9
16
 
10
- def define_connection(&block)
11
- if !@connection_type.nil?
12
- raise("#{name}'s connection type was already defined, can't redefine it!")
13
- end
14
- @connection_type = GraphQL::Relay::BaseConnection.create_type(self, &block)
17
+ # Define a custom edge type for this object type
18
+ def define_edge(**kwargs, &block)
19
+ GraphQL::Relay::EdgeType.create_type(self, **kwargs, &block)
15
20
  end
16
21
  end
@@ -8,7 +8,7 @@ module GraphQL
8
8
  #
9
9
  # `resolve` should return a hash with a key for each of the `return_field`s
10
10
  #
11
- # Inputs will also contain a `clientMutationId`
11
+ # Inputs may also contain a `clientMutationId`
12
12
  #
13
13
  # @example Updating the name of an item
14
14
  # UpdateNameMutation = GraphQL::Relay::Mutation.define do
@@ -94,7 +94,7 @@ module GraphQL
94
94
  GraphQL::ObjectType.define do
95
95
  name(type_name)
96
96
  description("Autogenerated return type of #{mutation_name}")
97
- field :clientMutationId, !types.String
97
+ field :clientMutationId, types.String
98
98
  type_fields.each do |name, field_obj|
99
99
  field name, field: field_obj
100
100
  end
@@ -110,7 +110,7 @@ module GraphQL
110
110
  GraphQL::InputObjectType.define do
111
111
  name(type_name)
112
112
  description("Autogenerated input type of #{mutation_name}")
113
- input_field :clientMutationId, !types.String
113
+ input_field :clientMutationId, types.String
114
114
  type_fields.each do |name, field_obj|
115
115
  input_field name, field_obj.type, field_obj.description, default_value: field_obj.default_value
116
116
  end
@@ -1,5 +1,9 @@
1
1
  module GraphQL
2
2
  module Relay
3
+ # A connection implementation to expose SQL collection objects.
4
+ # It works for:
5
+ # - `ActiveRecord::Relation`
6
+ # - `Sequel::Dataset`
3
7
  class RelationConnection < BaseConnection
4
8
  def cursor_from_node(item)
5
9
  offset = starting_offset + paged_nodes_array.index(item) + 1
@@ -93,5 +97,8 @@ module GraphQL
93
97
  if defined?(ActiveRecord)
94
98
  BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection)
95
99
  end
100
+ if defined?(Sequel)
101
+ BaseConnection.register_connection_implementation(Sequel::Dataset, RelationConnection)
102
+ end
96
103
  end
97
104
  end
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Relay
3
- VERSION = "0.10.0"
3
+ VERSION = "0.11.0"
4
4
  end
5
5
  end
@@ -0,0 +1,33 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::Relay::ConnectionType do
4
+ describe ".create_type" do
5
+ describe "connections with custom Edge classes / EdgeTypes" do
6
+ let(:query_string) {%|
7
+ {
8
+ rebels {
9
+ basesWithCustomEdge {
10
+ totalCountTimes100
11
+ edges {
12
+ upcasedName
13
+ edgeClassName
14
+ node {
15
+ name
16
+ }
17
+ }
18
+ }
19
+ }
20
+ }
21
+ |}
22
+
23
+ it "uses the custom edge and custom connection" do
24
+ result = query(query_string)
25
+ bases = result["data"]["rebels"]["basesWithCustomEdge"]
26
+ assert_equal 200, bases["totalCountTimes100"]
27
+ assert_equal ["YAVIN", "ECHO BASE"] , bases["edges"].map { |e| e["upcasedName"] }
28
+ assert_equal ["Yavin", "Echo Base"] , bases["edges"].map { |e| e["node"]["name"] }
29
+ assert_equal ["CustomBaseEdge", "CustomBaseEdge"] , bases["edges"].map { |e| e["edgeClassName"] }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,8 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe GraphQL::Relay::Mutation do
4
4
  let(:query_string) {%|
5
- mutation addBagel {
6
- introduceShip(input: {shipName: "Bagel", factionId: "1", clientMutationId: "1234"}) {
5
+ mutation addBagel($clientMutationId: String) {
6
+ introduceShip(input: {shipName: "Bagel", factionId: "1", clientMutationId: $clientMutationId}) {
7
7
  clientMutationId
8
8
  ship { name, id }
9
9
  faction { name }
@@ -18,8 +18,13 @@ describe GraphQL::Relay::Mutation do
18
18
  }
19
19
  |}
20
20
 
21
+ after do
22
+ STAR_WARS_DATA["Ship"].delete("9")
23
+ STAR_WARS_DATA["Faction"]["1"]["ships"].delete("9")
24
+ end
25
+
21
26
  it "returns the result & clientMutationId" do
22
- result = query(query_string)
27
+ result = query(query_string, "clientMutationId" => "1234")
23
28
  expected = {"data" => {
24
29
  "introduceShip" => {
25
30
  "clientMutationId" => "1234",
@@ -31,8 +36,20 @@ describe GraphQL::Relay::Mutation do
31
36
  }
32
37
  }}
33
38
  assert_equal(expected, result)
34
- # Cleanup:
35
- STAR_WARS_DATA["Ship"].delete("9")
36
- STAR_WARS_DATA["Faction"]["1"]["ships"].delete("9")
39
+ end
40
+
41
+ it "doesn't require a clientMutationId to perform mutations" do
42
+ result = query(query_string)
43
+ expected = {"data" => {
44
+ "introduceShip" => {
45
+ "clientMutationId" => nil,
46
+ "ship" => {
47
+ "name" => "Bagel",
48
+ "id" => NodeIdentification.to_global_id("Ship", "9"),
49
+ },
50
+ "faction" => {"name" => STAR_WARS_DATA["Faction"]["1"].name }
51
+ }
52
+ }}
53
+ assert_equal(expected, result)
37
54
  end
38
55
  end
@@ -20,7 +20,7 @@ describe GraphQL::Relay::RelationConnection do
20
20
  }
21
21
  }
22
22
 
23
- fragment basesConnection on BaseConnection {
23
+ fragment basesConnection on BasesConnectionWithTotalCount {
24
24
  totalCount,
25
25
  edges {
26
26
  cursor
@@ -160,7 +160,7 @@ describe GraphQL::Relay::RelationConnection do
160
160
  query getBases {
161
161
  empire {
162
162
  basesByName(first: 30) { ... basesFields }
163
- bases(first: 30) { ... basesFields }
163
+ bases(first: 30) { ... basesFields2 }
164
164
  }
165
165
  }
166
166
  fragment basesFields on BaseConnection {
@@ -170,6 +170,13 @@ describe GraphQL::Relay::RelationConnection do
170
170
  }
171
171
  }
172
172
  }
173
+ fragment basesFields2 on BasesConnectionWithTotalCount {
174
+ edges {
175
+ node {
176
+ name
177
+ }
178
+ }
179
+ }
173
180
  |}
174
181
 
175
182
  def get_names(result, field_name)
@@ -179,7 +186,6 @@ describe GraphQL::Relay::RelationConnection do
179
186
 
180
187
  it "applies the default value" do
181
188
  result = query(query_string)
182
-
183
189
  bases_by_id = ["Death Star", "Shield Generator", "Headquarters"]
184
190
  bases_by_name = ["Death Star", "Headquarters", "Shield Generator"]
185
191
 
@@ -187,4 +193,93 @@ describe GraphQL::Relay::RelationConnection do
187
193
  assert_equal(bases_by_name, get_names(result, "basesByName"))
188
194
  end
189
195
  end
196
+
197
+ describe "with a Sequel::Dataset" do
198
+ def get_names(result)
199
+ ships = result["data"]["empire"]["basesAsSequelDataset"]["edges"]
200
+ names = ships.map { |e| e["node"]["name"] }
201
+ end
202
+
203
+ def get_last_cursor(result)
204
+ result["data"]["empire"]["basesAsSequelDataset"]["edges"].last["cursor"]
205
+ end
206
+
207
+ describe "results" do
208
+ let(:query_string) {%|
209
+ query getShips($first: Int, $after: String, $last: Int, $before: String, $nameIncludes: String){
210
+ empire {
211
+ basesAsSequelDataset(first: $first, after: $after, last: $last, before: $before, nameIncludes: $nameIncludes) {
212
+ ... basesConnection
213
+ }
214
+ }
215
+ }
216
+
217
+ fragment basesConnection on BasesConnectionWithTotalCount {
218
+ totalCount,
219
+ edges {
220
+ cursor
221
+ node {
222
+ name
223
+ }
224
+ },
225
+ pageInfo {
226
+ hasNextPage
227
+ }
228
+ }
229
+ |}
230
+
231
+ it 'limits the result' do
232
+ result = query(query_string, "first" => 2)
233
+ assert_equal(2, get_names(result).length)
234
+
235
+ result = query(query_string, "first" => 3)
236
+ assert_equal(3, get_names(result).length)
237
+ end
238
+
239
+ it 'provides custom fileds on the connection type' do
240
+ result = query(query_string, "first" => 2)
241
+ assert_equal(
242
+ Base.where(faction_id: 2).count,
243
+ result["data"]["empire"]["basesAsSequelDataset"]["totalCount"]
244
+ )
245
+ end
246
+
247
+ it 'slices the result' do
248
+ result = query(query_string, "first" => 2)
249
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
250
+
251
+ # After the last result, find the next 2:
252
+ last_cursor = get_last_cursor(result)
253
+
254
+ result = query(query_string, "after" => last_cursor, "first" => 2)
255
+ assert_equal(["Headquarters"], get_names(result))
256
+
257
+ last_cursor = get_last_cursor(result)
258
+
259
+ result = query(query_string, "before" => last_cursor, "last" => 1)
260
+ assert_equal(["Shield Generator"], get_names(result))
261
+
262
+ result = query(query_string, "before" => last_cursor, "last" => 2)
263
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
264
+
265
+ result = query(query_string, "before" => last_cursor, "last" => 10)
266
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
267
+
268
+ end
269
+
270
+ it "applies custom arguments" do
271
+ result = query(query_string, "first" => 1, "nameIncludes" => "ea")
272
+ assert_equal(["Death Star"], get_names(result))
273
+
274
+ after = get_last_cursor(result)
275
+
276
+ result = query(query_string, "first" => 2, "nameIncludes" => "ea", "after" => after )
277
+ assert_equal(["Headquarters"], get_names(result))
278
+ before = get_last_cursor(result)
279
+
280
+ result = query(query_string, "last" => 1, "nameIncludes" => "ea", "before" => before)
281
+ assert_equal(["Death Star"], get_names(result))
282
+ end
283
+ end
284
+ end
190
285
  end
data/spec/spec_helper.rb CHANGED
@@ -2,6 +2,7 @@ require "codeclimate-test-reporter"
2
2
  CodeClimate::TestReporter.start
3
3
  require "sqlite3"
4
4
  require "active_record"
5
+ require "sequel"
5
6
  require "graphql/relay"
6
7
  require "minitest/autorun"
7
8
  require "minitest/focus"
@@ -16,5 +17,5 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
16
17
 
17
18
 
18
19
  def query(string, variables={})
19
- GraphQL::Query.new(StarWarsSchema, string, variables: variables, debug: true).result
20
+ GraphQL::Query.new(StarWarsSchema, string, variables: variables).result
20
21
  end
@@ -10,9 +10,10 @@ names = [
10
10
  'Executor',
11
11
  ]
12
12
 
13
- ## Set up "Bases" in ActiveRecord
14
13
  # ActiveRecord::Base.logger = Logger.new(STDOUT)
15
- ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
14
+ `rm -f ./_test_.db`
15
+ # Set up "Bases" in ActiveRecord
16
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "./_test_.db")
16
17
 
17
18
  ActiveRecord::Schema.define do
18
19
  self.verbose = false
@@ -32,6 +33,11 @@ Base.create!(name: "Death Star", planet: nil, faction_id: 2)
32
33
  Base.create!(name: "Shield Generator", planet: "Endor", faction_id: 2)
33
34
  Base.create!(name: "Headquarters", planet: "Coruscant", faction_id: 2)
34
35
 
36
+ # Also, set up Bases with Sequel
37
+ DB = Sequel.sqlite("./_test_.db")
38
+ class SequelBase < Sequel::Model(:bases)
39
+ end
40
+
35
41
  rebels = OpenStruct.new({
36
42
  id: '1',
37
43
  name: 'Alliance to Restore the Republic',
@@ -47,15 +47,38 @@ BaseType = GraphQL::ObjectType.define do
47
47
  field :planet, types.String
48
48
  end
49
49
 
50
- # Define a connection which will wrap an ActiveRecord::Relation.
51
- # We use an optional block to add fields to the connection type:
52
- BaseType.define_connection do
50
+ # Use an optional block to add fields to the connection type:
51
+ BaseConnectionWithTotalCountType = BaseType.define_connection do
52
+ name "BasesConnectionWithTotalCount"
53
53
  field :totalCount do
54
54
  type types.Int
55
55
  resolve -> (obj, args, ctx) { obj.object.count }
56
56
  end
57
57
  end
58
58
 
59
+ class CustomBaseEdge < GraphQL::Relay::Edge
60
+ def upcased_name
61
+ node.name.upcase
62
+ end
63
+ end
64
+
65
+ CustomBaseEdgeType = BaseType.define_edge do
66
+ name "CustomBaseEdge"
67
+ field :upcasedName, types.String, property: :upcased_name
68
+ field :edgeClassName, types.String do
69
+ resolve -> (obj, args, ctx) { obj.class.name }
70
+ end
71
+ end
72
+
73
+ CustomEdgeBaseConnectionType = BaseType.define_connection(edge_class: CustomBaseEdge, edge_type: CustomBaseEdgeType) do
74
+ name "CustomEdgeBaseConnection"
75
+
76
+ field :totalCountTimes100 do
77
+ type types.Int
78
+ resolve -> (obj, args, ctx) { obj.object.count * 100 }
79
+ end
80
+ end
81
+
59
82
 
60
83
  Faction = GraphQL::ObjectType.define do
61
84
  name "Faction"
@@ -75,7 +98,7 @@ Faction = GraphQL::ObjectType.define do
75
98
  # You can define arguments here and use them in the connection
76
99
  argument :nameIncludes, types.String
77
100
  end
78
- connection :bases, BaseType.connection_type do
101
+ connection :bases, BaseConnectionWithTotalCountType do
79
102
  # Resolve field should return an Array, the Connection
80
103
  # will do the rest!
81
104
  resolve -> (obj, args, ctx) {
@@ -107,6 +130,19 @@ Faction = GraphQL::ObjectType.define do
107
130
  connection :basesWithMaxLimitArray, BaseType.connection_type, max_page_size: 2 do
108
131
  resolve -> (object, args, context) { Base.all.to_a }
109
132
  end
133
+
134
+ connection :basesAsSequelDataset, BaseConnectionWithTotalCountType do
135
+ argument :nameIncludes, types.String
136
+ resolve -> (obj, args, ctx) {
137
+ all_bases = SequelBase.where(faction_id: obj.id)
138
+ if args[:nameIncludes]
139
+ all_bases = all_bases.where("name LIKE ?", "%#{args[:nameIncludes]}%")
140
+ end
141
+ all_bases
142
+ }
143
+ end
144
+
145
+ connection :basesWithCustomEdge, CustomEdgeBaseConnectionType, property: :bases
110
146
  end
111
147
 
112
148
  # Define a mutation. It will also:
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.10.0
4
+ version: 0.11.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-05-31 00:00:00.000000000 Z
11
+ date: 2016-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -178,6 +178,20 @@ dependencies:
178
178
  - - "~>"
179
179
  - !ruby/object:Gem::Version
180
180
  version: '11.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: sequel
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
181
195
  - !ruby/object:Gem::Dependency
182
196
  name: sqlite3
183
197
  requirement: !ruby/object:Gem::Requirement
@@ -206,8 +220,10 @@ files:
206
220
  - lib/graphql/relay/array_connection.rb
207
221
  - lib/graphql/relay/base_connection.rb
208
222
  - lib/graphql/relay/connection_field.rb
223
+ - lib/graphql/relay/connection_type.rb
209
224
  - lib/graphql/relay/define.rb
210
225
  - lib/graphql/relay/edge.rb
226
+ - lib/graphql/relay/edge_type.rb
211
227
  - lib/graphql/relay/global_id_field.rb
212
228
  - lib/graphql/relay/global_node_identification.rb
213
229
  - lib/graphql/relay/monkey_patches/base_type.rb
@@ -216,6 +232,7 @@ files:
216
232
  - lib/graphql/relay/relation_connection.rb
217
233
  - lib/graphql/relay/version.rb
218
234
  - spec/graphql/relay/array_connection_spec.rb
235
+ - spec/graphql/relay/connection_type_spec.rb
219
236
  - spec/graphql/relay/global_node_identification_spec.rb
220
237
  - spec/graphql/relay/mutation_spec.rb
221
238
  - spec/graphql/relay/page_info_spec.rb
@@ -249,6 +266,7 @@ specification_version: 4
249
266
  summary: Relay helpers for GraphQL
250
267
  test_files:
251
268
  - spec/graphql/relay/array_connection_spec.rb
269
+ - spec/graphql/relay/connection_type_spec.rb
252
270
  - spec/graphql/relay/global_node_identification_spec.rb
253
271
  - spec/graphql/relay/mutation_spec.rb
254
272
  - spec/graphql/relay/page_info_spec.rb