graphql-relay 0.4.2 → 0.4.3

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: 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