Skip to content

Commit f768ea0

Browse files
committed
refactor(Relay) use nodes for connection items naming
1 parent e060f9e commit f768ea0

File tree

9 files changed

+75
-71
lines changed

9 files changed

+75
-71
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@
88

99
### Bug fixes
1010

11+
## 0.18.1 (7 Aug 2016)
12+
13+
### Deprecations
14+
15+
- Unify `Relay` naming around `nodes` as the items of a connection:
16+
- `Relay::BaseConnection.connection_for_nodes` replaces `Relay::BaseConnection.connection_for_items`
17+
- `Relay::BaseConnection#nodes` replaces `Relay::BaseConnection#object`
18+
19+
### New features
20+
21+
- Connection fields' `.resolve_proc` is an instance of `Relay::ConnectionResolve` #204
22+
- Types, fields and arguments can store arbitrary values in their `metadata` hashes #203
23+
1124
## 0.18.0 (4 Aug 2016)
1225

1326
### Breaking changes

guides/relay.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,11 @@ Maybe you need to make a connection object yourself (for example, to return a co
295295
```ruby
296296
items = [...] # your collection objects
297297
args = {} # stub out arguments for this connection object
298-
connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
298+
connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(items)
299299
connection_class.new(items, args)
300300
```
301301

302-
`.connection_for_items` will return RelationConnection or ArrayConnection depending on `items`, then you can make a new connection
302+
`.connection_for_nodes` will return RelationConnection or ArrayConnection depending on `items`, then you can make a new connection
303303

304304
### Custom connections
305305

lib/graphql/relay/array_connection.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def has_previous_page
1818

1919
# apply first / last limit results
2020
def paged_nodes
21-
@paged_nodes = begin
21+
@paged_nodes ||= begin
2222
items = sliced_nodes
2323

2424
if limit
@@ -31,10 +31,7 @@ def paged_nodes
3131

3232
# Apply cursors to edges
3333
def sliced_nodes
34-
@sliced_nodes ||= begin
35-
items = object
36-
items[starting_offset..-1]
37-
end
34+
@sliced_nodes ||= nodes[starting_offset..-1]
3835
end
3936

4037
def index_from_cursor(cursor)

lib/graphql/relay/base_connection.rb

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,71 @@ module Relay
66
# - {#paged_nodes}, which applies `first` & `last` limits
77
#
88
# In a subclass, you have access to
9-
# - {#object}, the object which the connection will wrap
9+
# - {#nodes}, the collection which the connection will wrap
1010
# - {#first}, {#after}, {#last}, {#before} (arguments passed to the field)
1111
# - {#max_page_size} (the specified maximum page size that can be returned from a connection)
1212
#
1313
class BaseConnection
14+
extend Gem::Deprecate
15+
1416
# Just to encode data in the cursor, use something that won't conflict
1517
CURSOR_SEPARATOR = "---"
1618

1719
# Map of collection class names -> connection_classes
1820
# eg {"Array" => ArrayConnection}
1921
CONNECTION_IMPLEMENTATIONS = {}
2022

21-
# Find a connection implementation suitable for exposing `items`
22-
#
23-
# @param [Object] A collection of items (eg, Array, AR::Relation)
24-
# @return [subclass of BaseConnection] a connection Class for wrapping `items`
25-
def self.connection_for_items(items)
26-
# We check class membership by comparing class names rather than
27-
# identity to prevent this from being broken by Rails autoloading.
28-
# Changes to the source file for ItemsClass in Rails apps cause it to be
29-
# reloaded as a new object, so if we were to use `is_a?` here, it would
30-
# no longer match any registered custom connection types.
31-
ancestor_names = items.class.ancestors.map(&:name)
32-
implementation = CONNECTION_IMPLEMENTATIONS.find do |items_class_name, connection_class|
33-
ancestor_names.include? items_class_name
23+
class << self
24+
extend Gem::Deprecate
25+
26+
# Find a connection implementation suitable for exposing `nodes`
27+
#
28+
# @param [Object] A collection of nodes (eg, Array, AR::Relation)
29+
# @return [subclass of BaseConnection] a connection Class for wrapping `nodes`
30+
def connection_for_nodes(nodes)
31+
# Check for class _names_ because classes can be redefined in Rails development
32+
ancestor_names = nodes.class.ancestors.map(&:name)
33+
implementation = CONNECTION_IMPLEMENTATIONS.find do |nodes_class_name, connection_class|
34+
ancestor_names.include? nodes_class_name
35+
end
36+
if implementation.nil?
37+
raise("No connection implementation to wrap #{nodes.class} (#{nodes})")
38+
else
39+
implementation[1]
40+
end
3441
end
35-
if implementation.nil?
36-
raise("No connection implementation to wrap #{items.class} (#{items})")
37-
else
38-
implementation[1]
42+
43+
# Add `connection_class` as the connection wrapper for `nodes_class`
44+
# eg, `RelationConnection` is the implementation for `AR::Relation`
45+
# @param [Class] A class representing a collection (eg, Array, AR::Relation)
46+
# @param [Class] A class implementing Connection methods
47+
def register_connection_implementation(nodes_class, connection_class)
48+
CONNECTION_IMPLEMENTATIONS[nodes_class.name] = connection_class
3949
end
40-
end
4150

42-
# Add `connection_class` as the connection wrapper for `items_class`
43-
# eg, `RelationConnection` is the implementation for `AR::Relation`
44-
# @param [Class] A class representing a collection (eg, Array, AR::Relation)
45-
# @param [Class] A class implementing Connection methods
46-
def self.register_connection_implementation(items_class, connection_class)
47-
CONNECTION_IMPLEMENTATIONS[items_class.name] = connection_class
51+
# @deprecated use {#connection_for_nodes} instead
52+
alias :connection_for_items :connection_for_nodes
53+
deprecate(:connection_for_items, :connection_for_nodes, 2016, 9)
4854
end
4955

50-
attr_reader :object, :arguments, :max_page_size, :parent
56+
attr_reader :nodes, :arguments, :max_page_size, :parent
5157

52-
# Make a connection, wrapping `object`
53-
# @param The collection of results
58+
# Make a connection, wrapping `nodes`
59+
# @param [Object] The collection of nodes
5460
# @param Query arguments
5561
# @param max_page_size [Int] The maximum number of results to return
5662
# @param parent [Object] The object which this collection belongs to
57-
def initialize(object, arguments, max_page_size: nil, parent: nil)
58-
@object = object
63+
def initialize(nodes, arguments, max_page_size: nil, parent: nil)
64+
@nodes = nodes
5965
@arguments = arguments
6066
@max_page_size = max_page_size
6167
@parent = parent
6268
end
6369

70+
# @deprecated use {#nodes} instead
71+
alias :object :nodes
72+
deprecate(:object, :nodes, 2016, 9)
73+
6474
# Provide easy access to provided arguments:
6575
METHODS_FROM_ARGUMENTS = [:first, :after, :last, :before, :order]
6676

@@ -80,7 +90,7 @@ def initialize(object, arguments, max_page_size: nil, parent: nil)
8090
end
8191
end
8292

83-
# These are the items to render for this connection,
93+
# These are the nodes to render for this connection,
8494
# probably wrapped by {GraphQL::Relay::Edge}
8595
def edge_nodes
8696
@edge_nodes ||= paged_nodes
@@ -127,11 +137,11 @@ def cursor_from_node(object)
127137
private
128138

129139
def paged_nodes
130-
raise NotImplementedError, "must return items for this connection after paging"
140+
raise NotImplementedError, "must return nodes for this connection after paging"
131141
end
132142

133143
def sliced_nodes
134-
raise NotImplementedError, "must return all items for this connection after chopping off first and last"
144+
raise NotImplementedError, "must return all nodes for this connection after chopping off first and last"
135145
end
136146
end
137147
end

lib/graphql/relay/connection_field.rb

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
module GraphQL
22
module Relay
3-
# Provided a GraphQL field which returns a collection of items,
4-
# `ConnectionField.create` modifies that field to expose those items
3+
# Provided a GraphQL field which returns a collection of nodes,
4+
# `ConnectionField.create` modifies that field to expose those nodes
55
# as a collection.
66
#
7-
# The original resolve proc is used to fetch items,
8-
# then a connection implementation is fetched with {BaseConnection.connection_for_items}.
7+
# The original resolve proc is used to fetch nodes,
8+
# then a connection implementation is fetched with {BaseConnection.connection_for_nodes}.
99
class ConnectionField
1010
ARGUMENT_DEFINITIONS = [
1111
["first", GraphQL::INT_TYPE, "Returns the first _n_ elements from the list."],
@@ -27,24 +27,15 @@ class ConnectionField
2727
# Turn A GraphQL::Field into a connection by:
2828
# - Merging in the default arguments
2929
# - Transforming its resolve function to return a connection object
30-
# @param [GraphQL::Field] A field which returns items to be wrapped as a connection
31-
# @param max_page_size [Integer] The maximum number of items which may be requested (if a larger page is requested, it is limited to this number)
32-
# @return [GraphQL::Field] A field which serves a connections
30+
# @param [GraphQL::Field] A field which returns nodes to be wrapped as a connection
31+
# @param max_page_size [Integer] The maximum number of nodes which may be requested (if a larger page is requested, it is limited to this number)
32+
# @return [GraphQL::Field] The same field, modified to resolve to a connection object
3333
def self.create(underlying_field, max_page_size: nil)
3434
underlying_field.arguments = DEFAULT_ARGUMENTS.merge(underlying_field.arguments)
3535
original_resolve = underlying_field.resolve_proc
36-
underlying_field.resolve = get_connection_resolve(underlying_field.name, original_resolve, max_page_size: max_page_size)
36+
underlying_field.resolve = GraphQL::Relay::ConnectionResolve.new(underlying_field.name, original_resolve, max_page_size: max_page_size)
3737
underlying_field
3838
end
39-
40-
private
41-
42-
# Wrap the original resolve proc
43-
# so you capture its value, then wrap it in a
44-
# connection implementation
45-
def self.get_connection_resolve(field_name, underlying_resolve, max_page_size: nil)
46-
GraphQL::Relay::ConnectionResolve.new(field_name, underlying_resolve, max_page_size: max_page_size)
47-
end
4839
end
4940
end
5041
end

lib/graphql/relay/connection_resolve.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ def initialize(field_name, underlying_resolve, max_page_size: nil)
88
end
99

1010
def call(obj, args, ctx)
11-
items = @underlying_resolve.call(obj, args, ctx)
12-
connection_class = GraphQL::Relay::BaseConnection.connection_for_items(items)
13-
connection_class.new(items, args, max_page_size: @max_page_size, parent: obj)
11+
nodes = @underlying_resolve.call(obj, args, ctx)
12+
connection_class = GraphQL::Relay::BaseConnection.connection_for_nodes(nodes)
13+
connection_class.new(nodes, args, max_page_size: @max_page_size, parent: obj)
1414
end
1515
end
1616
end

lib/graphql/relay/relation_connection.rb

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,12 @@ def has_previous_page
2222

2323
# apply first / last limit results
2424
def paged_nodes
25-
@paged_nodes ||= begin
26-
items = sliced_nodes
27-
items.limit(limit)
28-
end
25+
@paged_nodes ||= sliced_nodes.limit(limit)
2926
end
3027

3128
# Apply cursors to edges
3229
def sliced_nodes
33-
@sliced_nodes ||= begin
34-
items = object
35-
items.offset(starting_offset)
36-
end
30+
@sliced_nodes ||= nodes.offset(starting_offset)
3731
end
3832

3933
def offset_from_cursor(cursor)

readme.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,3 @@ If you're building a backend for [Relay](http://facebook.github.io/relay/), you'
142142
- Reduce duplication in ArrayConnection / RelationConnection
143143
- Improve API for creating edges (better RANGE_ADD support)
144144
- If the new edge isn't a member of the connection's objects, raise a nice error
145-
- Rename `Connection#object` => `Connection#collection` with deprecation

spec/support/star_wars_schema.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
name "BasesConnectionWithTotalCount"
4848
field :totalCount do
4949
type types.Int
50-
resolve -> (obj, args, ctx) { obj.object.count }
50+
resolve -> (obj, args, ctx) { obj.nodes.count }
5151
end
5252
end
5353

@@ -75,7 +75,7 @@ def upcased_parent_name
7575

7676
field :totalCountTimes100 do
7777
type types.Int
78-
resolve -> (obj, args, ctx) { obj.object.count * 100 }
78+
resolve -> (obj, args, ctx) { obj.nodes.count * 100 }
7979
end
8080
end
8181

0 commit comments

Comments
 (0)