graphql-relay 0.4.2 → 0.4.3

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: b27b8ff701421ecd919a8cb16dfe819e42925769
4
- data.tar.gz: c87a89aa53e9940ca8c5cf6a822aa3f73f8fec8b
3
+ metadata.gz: 1038ccd66be579e1ade39b942de12fd993cbdc26
4
+ data.tar.gz: 278b0a2e1dbbe0ddb15669e6fcfb0e4206bbab0f
5
5
  SHA512:
6
- metadata.gz: badde6bd6ab29ab7ce5ae3366163491a888e40105cfbf414bbe5c54583d8441a1103d5a8a0fb195df663b410befd9f469362cb9ba5d2dfb3991363b21abd904f
7
- data.tar.gz: 9d8a4799e0a570a3be0bc58aa026c9a7e87531cd7caf17705e845f0147c97d11b2e5a10bdca0ece40cfc7994435f1d7f4f162574a31113370950a53753493dd2
6
+ metadata.gz: e8bf419f8e9c9c55d93a2c1efe15e3e66e81772ef04fe537046205d5d42f1c84bda9c690b3075625f6d80947d2b6fcce9df7032d98507219a70426af26db85ed
7
+ data.tar.gz: ee7c7e669c03577c0924838f6a985b0639ee9008a5e0fbdfaa24eec75f3913e713b41eeb362802cf42eb2f32b2fd1ac851e74efdb6a2da9c59f2d5ea9ad97dd7
data/README.md CHANGED
@@ -44,6 +44,8 @@ Examples:
44
44
 
45
45
  You can also add custom fields to connection objects: [Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/master/spec/support/star_wars_schema.rb#L36-L43)
46
46
 
47
+ At runtime, `graphql-relay` chooses a connection implementation to expose a collection. `graphql-relay` provides `ArrayConnection` and `RelationConnection`, but you can also provide your own with `BaseConnection.register_connection_implementation`.
48
+
47
49
  ### Mutations
48
50
 
49
51
  Mutations allow Relay to mutate your system. When you define a mutation, you'll be defining:
@@ -71,6 +73,7 @@ Examples:
71
73
  ## Todo
72
74
 
73
75
  - Show how to replace default connection implementations with custom ones
76
+ - Show how to wrap a simple field in a connection field with `ConnectionField.create`
74
77
 
75
78
  ## More Resources
76
79
 
@@ -31,7 +31,10 @@ module GraphQL
31
31
  connection_type
32
32
  end
33
33
 
34
- # @return [subclass of BaseConnection] a connection wrapping `items`
34
+ # Find a connection implementation suitable for exposing `items`
35
+ #
36
+ # @param [Object] A collection of items (eg, Array, AR::Relation)
37
+ # @return [subclass of BaseConnection] a connection Class for wrapping `items`
35
38
  def self.connection_for_items(items)
36
39
  implementation = CONNECTION_IMPLEMENTATIONS.find do |items_class, connection_class|
37
40
  items.is_a?(items_class)
@@ -45,6 +48,8 @@ module GraphQL
45
48
 
46
49
  # Add `connection_class` as the connection wrapper for `items_class`
47
50
  # eg, `RelationConnection` is the implementation for `AR::Relation`
51
+ # @param [Class] A class representing a collection (eg, Array, AR::Relation)
52
+ # @param [Class] A class implementing Connection methods
48
53
  def self.register_connection_implementation(items_class, connection_class)
49
54
  CONNECTION_IMPLEMENTATIONS[items_class] = connection_class
50
55
  end
@@ -59,6 +64,16 @@ module GraphQL
59
64
  # Provide easy access to provided arguments:
60
65
  METHODS_FROM_ARGUMENTS = [:first, :after, :last, :before, :order]
61
66
 
67
+ # @!method first
68
+ # The value passed as `first:`, if there was one
69
+ # @!method after
70
+ # The value passed as `after:`, if there was one
71
+ # @!method last
72
+ # The value passed as `last:`, if there was one
73
+ # @!method before
74
+ # The value passed as `before:`, if there was one
75
+ # @!method order
76
+ # The value passed as `order:`, if there was one
62
77
  METHODS_FROM_ARGUMENTS.each do |arg_name|
63
78
  define_method(arg_name) do
64
79
  arguments[arg_name]
@@ -77,12 +92,12 @@ module GraphQL
77
92
 
78
93
  # Used by `pageInfo`
79
94
  def has_next_page
80
- first && sliced_nodes.count > first
95
+ !!(first && sliced_nodes.count > first)
81
96
  end
82
97
 
83
98
  # Used by `pageInfo`
84
99
  def has_previous_page
85
- last && sliced_nodes.count > last
100
+ !!(last && sliced_nodes.count > last)
86
101
  end
87
102
 
88
103
  # An opaque operation which returns a connection-specific cursor.
@@ -93,11 +108,11 @@ module GraphQL
93
108
  private
94
109
 
95
110
  def paged_nodes
96
- raise NotImplementedError, "must items for this connection after paging"
111
+ raise NotImplementedError, "must return items for this connection after paging"
97
112
  end
98
113
 
99
114
  def sliced_nodes
100
- raise NotImplementedError, "must all items for this connection after chopping off first and last"
115
+ raise NotImplementedError, "must return all items for this connection after chopping off first and last"
101
116
  end
102
117
  end
103
118
  end
@@ -1,5 +1,11 @@
1
1
  module GraphQL
2
2
  module Relay
3
+ # Provided a GraphQL field which returns a collection of items,
4
+ # `ConnectionField.create` modifies that field to expose those items
5
+ # as a collection.
6
+ #
7
+ # The original resolve proc is used to fetch items,
8
+ # then a connection implementation is fetched with {BaseConnection.connection_for_items}.
3
9
  class ConnectionField
4
10
  ARGUMENT_DEFINITIONS = [
5
11
  [:first, GraphQL::INT_TYPE],
@@ -18,8 +24,10 @@ module GraphQL
18
24
  end
19
25
 
20
26
  # Turn A GraphQL::Field into a connection by:
21
- # - Merging in the default argument
27
+ # - Merging in the default arguments
22
28
  # - Transforming its resolve function to return a connection object
29
+ # @param [GraphQL::Field] A field which returns items to be wrapped as a connection
30
+ # @return [GraphQL::Field] A field which serves a connections
23
31
  def self.create(underlying_field)
24
32
  underlying_field.arguments = underlying_field.arguments.merge(DEFAULT_ARGUMENTS)
25
33
  # TODO: make a public API on GraphQL::Field to expose this proc
@@ -30,6 +38,9 @@ module GraphQL
30
38
 
31
39
  private
32
40
 
41
+ # Wrap the original resolve proc
42
+ # so you capture its value, then wrap it in a
43
+ # connection implementation
33
44
  def self.get_connection_resolve(field_name, underlying_resolve)
34
45
  -> (obj, args, ctx) {
35
46
  items = underlying_resolve.call(obj, args, ctx)
@@ -1,8 +1,5 @@
1
1
  class GraphQL::DefinitionHelpers::DefinedByConfig::DefinitionConfig
2
2
  # Wraps a field definition with a ConnectionField
3
- # - applies default fields
4
- # - wraps the resolve proc to make a connection
5
- #
6
3
  def connection(name, type = nil, desc = nil, property: nil, &block)
7
4
  underlying_field = field(name, type, desc, property: property, &block)
8
5
  connection_field = GraphQL::Relay::ConnectionField.create(underlying_field)
@@ -11,7 +11,6 @@ module GraphQL
11
11
  @order ||= (super || "id")
12
12
  end
13
13
 
14
-
15
14
  private
16
15
 
17
16
  # apply first / last limit results
@@ -19,7 +18,7 @@ module GraphQL
19
18
  @paged_nodes = begin
20
19
  items = sliced_nodes
21
20
  first && items = items.first(first)
22
- last && items.length > last && items.last(last)
21
+ last && items.count > last && items = items.last(last)
23
22
  items
24
23
  end
25
24
  end
@@ -71,6 +70,8 @@ module GraphQL
71
70
  ["#{name} #{direction_marker} ?", value]
72
71
  end
73
72
  end
73
+
74
+
74
75
  if defined?(ActiveRecord)
75
76
  BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection)
76
77
  end
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Relay
3
- VERSION = '0.4.2'
3
+ VERSION = '0.4.3'
4
4
  end
5
5
  end
@@ -4,12 +4,34 @@ describe GraphQL::Relay::GlobalNodeIdentification do
4
4
  let(:node_identification) { NodeIdentification }
5
5
  describe 'NodeField' do
6
6
  it 'finds objects by id' do
7
- global_id = node_identification.to_global_id("Ship", "1")
8
- result = query(%|{node(id: "#{global_id}") { id, ... on Ship { name } }}|)
7
+ global_id = node_identification.to_global_id("Faction", "1")
8
+ result = query(%|{
9
+ node(id: "#{global_id}") {
10
+ id,
11
+ ... on Faction {
12
+ name
13
+ ships(first: 1) {
14
+ edges {
15
+ node {
16
+ name
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }|)
9
23
  expected = {"data" => {
10
- "node" => {
11
- "id" => global_id,
12
- "name" => "X-Wing"
24
+ "node"=>{
25
+ "id"=>"RmFjdGlvbi0x",
26
+ "name"=>"Alliance to Restore the Republic",
27
+ "ships"=>{
28
+ "edges"=>[
29
+ {"node"=>{
30
+ "name" => "X-Wing"
31
+ }
32
+ }
33
+ ]
34
+ }
13
35
  }
14
36
  }}
15
37
  assert_equal(expected, result)
@@ -0,0 +1,65 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::Relay::PageInfo do
4
+ def get_page_info(result)
5
+ result["data"]["empire"]["bases"]["pageInfo"]
6
+ end
7
+
8
+ def get_last_cursor(result)
9
+ result["data"]["empire"]["bases"]["edges"].last["cursor"]
10
+ end
11
+
12
+ let(:cursor_of_last_base) {
13
+ result = query(query_string, "first" => 3)
14
+ last_cursor = get_last_cursor(result)
15
+ }
16
+
17
+ let(:query_string) {%|
18
+ query getShips($first: Int, $after: String, $last: Int, $before: String, $order: String, $nameIncludes: String){
19
+ empire {
20
+ bases(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
21
+ edges {
22
+ cursor
23
+ }
24
+ pageInfo {
25
+ hasNextPage
26
+ hasPreviousPage
27
+ }
28
+ }
29
+ }
30
+ }
31
+ |}
32
+
33
+ describe 'hasNextPage / hasPreviousPage' do
34
+ it "hasNextPage is true if there are more items" do
35
+ result = query(query_string, "first" => 2)
36
+ assert_equal(true, get_page_info(result)["hasNextPage"])
37
+ assert_equal(false, get_page_info(result)["hasPreviousPage"], "hasPreviousPage is false if 'last' is missing")
38
+
39
+ last_cursor = get_last_cursor(result)
40
+ result = query(query_string, "first" => 100, "after" => last_cursor)
41
+ assert_equal(false, get_page_info(result)["hasNextPage"])
42
+ assert_equal(false, get_page_info(result)["hasPreviousPage"])
43
+ end
44
+
45
+ it "hasPreviousPage if there are more items" do
46
+ result = query(query_string, "last" => 100, "before" => cursor_of_last_base)
47
+ assert_equal(false, get_page_info(result)["hasNextPage"])
48
+ assert_equal(false, get_page_info(result)["hasPreviousPage"])
49
+
50
+ result = query(query_string, "last" => 1, "before" => cursor_of_last_base)
51
+ assert_equal(false, get_page_info(result)["hasNextPage"])
52
+ assert_equal(true, get_page_info(result)["hasPreviousPage"])
53
+ end
54
+
55
+
56
+ it "has both if first and last are present" do
57
+ result = query(query_string, "last" => 1, "first" => 1, "before" => cursor_of_last_base)
58
+ assert_equal(true, get_page_info(result)["hasNextPage"])
59
+ assert_equal(true, get_page_info(result)["hasPreviousPage"])
60
+ end
61
+ end
62
+
63
+
64
+
65
+ end
@@ -6,10 +6,6 @@ describe GraphQL::Relay::RelationConnection do
6
6
  names = ships.map { |e| e["node"]["name"] }
7
7
  end
8
8
 
9
- def get_page_info(result)
10
- result["data"]["empire"]["bases"]["pageInfo"]
11
- end
12
-
13
9
  def get_last_cursor(result)
14
10
  result["data"]["empire"]["bases"]["edges"].last["cursor"]
15
11
  end
@@ -20,9 +16,6 @@ describe GraphQL::Relay::RelationConnection do
20
16
  empire {
21
17
  bases(first: $first, after: $after, last: $last, before: $before, order: $order, nameIncludes: $nameIncludes) {
22
18
  ... basesConnection
23
- pageInfo {
24
- hasNextPage
25
- }
26
19
  }
27
20
  }
28
21
  }
@@ -46,14 +39,6 @@ describe GraphQL::Relay::RelationConnection do
46
39
  assert_equal(3, get_names(result).length)
47
40
  end
48
41
 
49
- it 'provides pageInfo' do
50
- result = query(query_string, "first" => 2)
51
- assert_equal(true, get_page_info(result)["hasNextPage"])
52
-
53
- result = query(query_string, "first" => 100)
54
- assert_equal(false, get_page_info(result)["hasNextPage"])
55
- end
56
-
57
42
  it 'provides custom fileds on the connection type' do
58
43
  result = query(query_string, "first" => 2)
59
44
  assert_equal(
@@ -72,8 +57,12 @@ describe GraphQL::Relay::RelationConnection do
72
57
  result = query(query_string, "after" => last_cursor, "first" => 2)
73
58
  assert_equal(["Headquarters"], get_names(result))
74
59
 
60
+ last_cursor = get_last_cursor(result)
61
+ result = query(query_string, "before" => last_cursor, "last" => 1)
62
+ assert_equal(["Shield Generator"], get_names(result))
63
+
75
64
  result = query(query_string, "before" => last_cursor, "last" => 2)
76
- assert_equal(["Death Star"], get_names(result))
65
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
77
66
  end
78
67
 
79
68
  it 'paginates with order' do
@@ -97,7 +86,7 @@ describe GraphQL::Relay::RelationConnection do
97
86
  assert_equal(["Death Star", "Headquarters"], get_names(result))
98
87
 
99
88
  # After the last result, find the next 2:
100
- last_cursor = result["data"]["empire"]["bases"]["edges"].last["cursor"]
89
+ last_cursor = get_last_cursor(result)
101
90
 
102
91
  result = query(query_string, "after" => last_cursor, "first" => 2, "order" => "name")
103
92
  assert_equal(["Shield Generator"], get_names(result))
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.2
4
+ version: 0.4.3
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-09-15 00:00:00.000000000 Z
11
+ date: 2015-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -203,6 +203,7 @@ files:
203
203
  - spec/graphql/relay/array_connection_spec.rb
204
204
  - spec/graphql/relay/global_node_identification_spec.rb
205
205
  - spec/graphql/relay/mutation_spec.rb
206
+ - spec/graphql/relay/page_info_spec.rb
206
207
  - spec/graphql/relay/relation_connection_spec.rb
207
208
  - spec/spec_helper.rb
208
209
  - spec/support/star_wars_data.rb
@@ -235,6 +236,7 @@ test_files:
235
236
  - spec/graphql/relay/array_connection_spec.rb
236
237
  - spec/graphql/relay/global_node_identification_spec.rb
237
238
  - spec/graphql/relay/mutation_spec.rb
239
+ - spec/graphql/relay/page_info_spec.rb
238
240
  - spec/graphql/relay/relation_connection_spec.rb
239
241
  - spec/spec_helper.rb
240
242
  - spec/support/star_wars_data.rb