graphql-relay 0.2.0 → 0.3.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: 6284053a13755747eea7b324a8c95c6acac7a2ce
4
- data.tar.gz: 12fd8d7abd63b48100a90212fc2386762129fdd9
3
+ metadata.gz: 4a86533b3e1bc7030fe0016ee41e9492e25cc597
4
+ data.tar.gz: d80d3d7006d8f37a7f208b49c2fd3dc5f78081c2
5
5
  SHA512:
6
- metadata.gz: 0df6bf1e5c4f2ac0d10e8f1328159fab1c802e969c26c3c274c3e1075b525aca788b21f24cb8cd1a0ae636838143d00d9c4e83441b94735431ae08c00c288165
7
- data.tar.gz: 4a9e932f728d71c8763c072b4d6e419b1d37d5da5607fe68deb7bf87a4c1850ae19ba1328d2a0483accedfcea35ab4e4820d8d76202891363e85ed46cbae3121
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 `GraphQL::Relay::GlobalIdField.new(type_name)`. [Example](https://github.com/rmosolgo/graphql-relay-ruby/blob/120b750cf86f1eb5c9997b588f022b2ef3a0012c/spec/support/star_wars_schema.rb#L22)
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
- - use the newly-created connection type as the field's return type
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/120b750cf86f1eb5c9997b588f022b2ef3a0012c/spec/support/star_wars_schema.rb#L48-L61)
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
- - [ ] pluralIdentifyingRootField
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
- underlying_field = field(name, type, desc, property: property, &block)
8
- connection_field = GraphQL::Relay::ConnectionField.create(underlying_field)
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
@@ -0,0 +1,7 @@
1
+ module GraphQL
2
+ class ObjectType
3
+ def connection_type
4
+ @connection_type ||= GraphQL::Relay::BaseConnection.create_type(self)
5
+ end
6
+ end
7
+ 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'
@@ -44,5 +44,6 @@ module GraphQL
44
44
  index.to_i
45
45
  end
46
46
  end
47
+ BaseConnection.register_connection_implementation(Array, ArrayConnection)
47
48
  end
48
49
  end
@@ -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 = ConnectionType.define do
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)
@@ -71,5 +71,8 @@ module GraphQL
71
71
  ["#{name} #{direction_marker} ?", value]
72
72
  end
73
73
  end
74
+ if defined?(ActiveRecord)
75
+ BaseConnection.register_connection_implementation(ActiveRecord::Relation, RelationConnection)
76
+ end
74
77
  end
75
78
  end
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module Relay
3
- VERSION = '0.2.0'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
@@ -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["data"]["rebels"]["ships"]["edges"].last["cursor"]
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
- edges {
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
- field :id, field: GraphQL::Relay::GlobalIdField.new("Base")
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, ShipConnection do
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, BaseConnection do
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.2.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-24 00:00:00.000000000 Z
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