graphql-relay 0.10.0 → 0.11.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 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