graphql_includable 0.5.0 → 1.0.0.beta.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 +4 -4
- data/lib/graphql_includable/includes.rb +67 -0
- data/lib/graphql_includable/includes_builder.rb +108 -0
- data/lib/graphql_includable/relay/edge_with_node.rb +25 -2
- data/lib/graphql_includable/relay/edge_with_node_connection.rb +16 -6
- data/lib/graphql_includable/relay/edge_with_node_connection_type.rb +2 -0
- data/lib/graphql_includable/relay/instrumentation.rb +77 -0
- data/lib/graphql_includable/resolver.rb +129 -141
- data/lib/graphql_includable.rb +33 -25
- metadata +7 -15
- data/lib/graphql_includable/concern.rb +0 -39
- data/lib/graphql_includable/edge.rb +0 -108
- data/lib/graphql_includable/new/gql_includable.rb +0 -49
- data/lib/graphql_includable/new/includes.rb +0 -69
- data/lib/graphql_includable/new/includes_builder.rb +0 -110
- data/lib/graphql_includable/new/relay/edge_with_node.rb +0 -48
- data/lib/graphql_includable/new/relay/edge_with_node_connection.rb +0 -101
- data/lib/graphql_includable/new/relay/edge_with_node_connection_type.rb +0 -37
- data/lib/graphql_includable/new/relay/instrumentation.rb +0 -79
- data/lib/graphql_includable/new/resolver.rb +0 -203
- data/lib/graphql_includable/relay/instrumentation/connection.rb +0 -83
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8ee024cd716dce8f0dee9b91066c089ba8ffab7
|
4
|
+
data.tar.gz: 5c6b1fb71d7c754f3f94285bf9571c72583667ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8cf0f027b8cf3a042053bfec69b180e5063e15df750f8e07b7c66c2d187ba3a26c25c8efb5231226ed2bacb814293660426ebca41af8bfff52c310d024bd3ec4
|
7
|
+
data.tar.gz: 402967a9b2a4d9675a718f8aed1314cfaf5793dade58ebe7e645ede3ab702a50ee2f18846ca10c89b64a33927d208c30bc70cde5c3c07da4a46b281e8f84a370
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module GraphQLIncludable
|
2
|
+
class Includes
|
3
|
+
attr_reader :included_children
|
4
|
+
|
5
|
+
def initialize(parent_attribute)
|
6
|
+
@parent_attribute = parent_attribute
|
7
|
+
@included_children = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_child(key)
|
11
|
+
return @included_children[key] if @included_children.key?(key)
|
12
|
+
manager = Includes.new(key)
|
13
|
+
@included_children[key] = manager
|
14
|
+
manager
|
15
|
+
end
|
16
|
+
|
17
|
+
def merge_includes(includes_manager)
|
18
|
+
includes_manager.included_children.each do |key, manager|
|
19
|
+
included_children[key] = if included_children.key?(key)
|
20
|
+
included_children[key].merge_includes(manager)
|
21
|
+
else
|
22
|
+
manager
|
23
|
+
end
|
24
|
+
end
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](key)
|
29
|
+
@included_children[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
def dig(*args)
|
33
|
+
args = args[0] if args.length == 1 && args[0].is_a?(Array)
|
34
|
+
return @included_children if args.empty?
|
35
|
+
@included_children.dig(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def empty?
|
39
|
+
@included_children.empty?
|
40
|
+
end
|
41
|
+
|
42
|
+
def active_record_includes
|
43
|
+
child_includes = {}
|
44
|
+
child_includes_arr = []
|
45
|
+
@included_children.each do |key, value|
|
46
|
+
if value.empty?
|
47
|
+
child_includes_arr << key
|
48
|
+
else
|
49
|
+
active_record_includes = value.active_record_includes
|
50
|
+
if active_record_includes.is_a?(Array)
|
51
|
+
child_includes_arr += active_record_includes
|
52
|
+
else
|
53
|
+
child_includes.merge!(active_record_includes)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if child_includes_arr.present?
|
59
|
+
child_includes_arr << child_includes if child_includes.present?
|
60
|
+
child_includes = child_includes_arr
|
61
|
+
end
|
62
|
+
|
63
|
+
return child_includes if @parent_attribute.nil?
|
64
|
+
{ @parent_attribute => child_includes }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module GraphQLIncludable
|
2
|
+
class IncludesBuilder
|
3
|
+
attr_reader :included_path, :includes
|
4
|
+
|
5
|
+
def initialize(only_one_path: true)
|
6
|
+
@only_one_path = only_one_path
|
7
|
+
@included_path = []
|
8
|
+
@includes = GraphQLIncludable::Includes.new(nil)
|
9
|
+
end
|
10
|
+
|
11
|
+
def includes?
|
12
|
+
@included_path.present?
|
13
|
+
end
|
14
|
+
|
15
|
+
def active_record_includes
|
16
|
+
@includes.active_record_includes
|
17
|
+
end
|
18
|
+
|
19
|
+
def path_leaf_includes
|
20
|
+
@includes.dig(included_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def path(*symbols, &block)
|
24
|
+
raise ArgumentError, 'Can only add path once' if @included_path.present? && @only_one_path
|
25
|
+
|
26
|
+
if symbols.present?
|
27
|
+
first, *rest = symbols
|
28
|
+
includes = @includes.add_child(first)
|
29
|
+
rest.each do |key|
|
30
|
+
includes = includes.add_child(key)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
includes = @includes
|
34
|
+
end
|
35
|
+
|
36
|
+
if block_given?
|
37
|
+
nested = GraphQLIncludable::IncludesBuilder.new
|
38
|
+
nested.instance_eval(&block)
|
39
|
+
symbols += nested.included_path
|
40
|
+
includes.merge_includes(nested.includes)
|
41
|
+
end
|
42
|
+
@included_path = symbols
|
43
|
+
end
|
44
|
+
|
45
|
+
def sibling_path(*symbols, &block)
|
46
|
+
if symbols.present?
|
47
|
+
first, *rest = symbols
|
48
|
+
includes = @includes.add_child(first)
|
49
|
+
rest.each do |key|
|
50
|
+
includes = includes.add_child(key)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
includes = @includes
|
54
|
+
end
|
55
|
+
|
56
|
+
return unless block_given?
|
57
|
+
nested = GraphQLIncludable::IncludesBuilder.new(only_one_path: false)
|
58
|
+
nested.instance_eval(&block)
|
59
|
+
includes.merge_includes(nested.includes)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ConnectionIncludesBuilder
|
64
|
+
attr_reader :nodes_builder, :edges_builder
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
@nodes_builder = IncludesBuilder.new
|
68
|
+
@edges_builder = ConnectionEdgesIncludesBuilder.new
|
69
|
+
end
|
70
|
+
|
71
|
+
def includes?
|
72
|
+
@nodes_builder.includes? || @edges_builder.includes?
|
73
|
+
end
|
74
|
+
|
75
|
+
def nodes(*symbols, &block)
|
76
|
+
@nodes_builder.path(*symbols, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def edges(&block)
|
80
|
+
@edges_builder.instance_eval(&block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ConnectionEdgesIncludesBuilder
|
85
|
+
attr_reader :builder, :node_builder
|
86
|
+
|
87
|
+
def initialize
|
88
|
+
@builder = IncludesBuilder.new
|
89
|
+
@node_builder = IncludesBuilder.new
|
90
|
+
end
|
91
|
+
|
92
|
+
def includes?
|
93
|
+
@builder.includes? && @node_builder.includes?
|
94
|
+
end
|
95
|
+
|
96
|
+
def path(*symbols, &block)
|
97
|
+
@builder.path(*symbols, &block)
|
98
|
+
end
|
99
|
+
|
100
|
+
def sibling_path(*symbols, &block)
|
101
|
+
@builder.sibling_path(*symbols, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def node(*symbols, &block)
|
105
|
+
@node_builder.path(*symbols, &block)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -1,10 +1,33 @@
|
|
1
|
+
GraphQL::Field.accepts_definitions(
|
2
|
+
##
|
3
|
+
# Define how to get from an edge Active Record model to the node Active Record model
|
4
|
+
connection_properties: GraphQL::Define.assign_metadata_key(:connection_properties),
|
5
|
+
|
6
|
+
##
|
7
|
+
# Define a resolver for connection edges records
|
8
|
+
resolve_edges: GraphQL::Define.assign_metadata_key(:resolve_edges),
|
9
|
+
|
10
|
+
##
|
11
|
+
# Define a resolver for connection nodes records
|
12
|
+
resolve_nodes: GraphQL::Define.assign_metadata_key(:resolve_nodes),
|
13
|
+
|
14
|
+
##
|
15
|
+
# Internally used to mark a connection type that has a fetched edge
|
16
|
+
_includable_connection_marker: GraphQL::Define.assign_metadata_key(:_includable_connection_marker)
|
17
|
+
)
|
18
|
+
|
1
19
|
module GraphQLIncludable
|
2
20
|
module Relay
|
3
21
|
class EdgeWithNode < GraphQL::Relay::Edge
|
4
22
|
def initialize(node, connection)
|
5
23
|
@edge = node
|
6
|
-
|
7
|
-
super(
|
24
|
+
@edge_to_node = ->() { connection.edge_to_node(@edge) }
|
25
|
+
super(nil, connection)
|
26
|
+
end
|
27
|
+
|
28
|
+
def node
|
29
|
+
@node ||= @edge_to_node.call
|
30
|
+
@node
|
8
31
|
end
|
9
32
|
|
10
33
|
def method_missing(method_name, *args, &block)
|
@@ -11,8 +11,8 @@ module GraphQLIncludable
|
|
11
11
|
@parent = parent
|
12
12
|
@args = args
|
13
13
|
@ctx = ctx
|
14
|
-
@edges_property = edges_property
|
15
|
-
@nodes_property = nodes_property
|
14
|
+
@edges_property = edges_property # optional
|
15
|
+
@nodes_property = nodes_property # optional
|
16
16
|
@edge_to_node_property = edge_to_node_property
|
17
17
|
@edges_resolver = edges_resolver
|
18
18
|
@nodes_resolver = nodes_resolver
|
@@ -33,14 +33,20 @@ module GraphQLIncludable
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def fetch_edges
|
36
|
+
# This context is used within Resolver for connections
|
37
|
+
ctx.namespace(:gql_includable)[:resolving] = :edges
|
36
38
|
@loaded_edges ||= @edges_and_nodes.edges_resolver.call(@edges_and_nodes.parent, args, ctx)
|
39
|
+
ctx.namespace(:gql_includable)[:resolving] = nil
|
37
40
|
# Set nodes to make underlying BaseConnection work
|
38
41
|
@nodes = @loaded_edges
|
39
42
|
@loaded_edges
|
40
43
|
end
|
41
44
|
|
42
45
|
def fetch_nodes
|
46
|
+
# This context is used within Resolver for connections
|
47
|
+
ctx.namespace(:gql_includable)[:resolving] = :nodes
|
43
48
|
@loaded_nodes ||= @edges_and_nodes.nodes_resolver.call(@edges_and_nodes.parent, args, ctx)
|
49
|
+
ctx.namespace(:gql_includable)[:resolving] = nil
|
44
50
|
# Set nodes to make underlying BaseConnection work
|
45
51
|
@nodes = @loaded_nodes
|
46
52
|
@loaded_nodes
|
@@ -76,11 +82,15 @@ module GraphQLIncludable
|
|
76
82
|
return @loaded_nodes if @loaded_nodes.present?
|
77
83
|
return @loaded_edges if @loaded_edges.present?
|
78
84
|
|
79
|
-
|
80
|
-
|
85
|
+
if @edges_and_nodes.nodes_property.present?
|
86
|
+
nodes_preloaded = @edges_and_nodes.parent.association(@edges_and_nodes.nodes_property).loaded?
|
87
|
+
return fetch_nodes if nodes_preloaded
|
88
|
+
end
|
81
89
|
|
82
|
-
|
83
|
-
|
90
|
+
if @edges_and_nodes.edges_property.present?
|
91
|
+
edges_preloaded = @edges_and_nodes.parent.association(@edges_and_nodes.edges_property).loaded?
|
92
|
+
return fetch_edges if edges_preloaded
|
93
|
+
end
|
84
94
|
|
85
95
|
fetch_nodes
|
86
96
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative 'edge_with_node_connection'
|
2
|
+
|
3
|
+
module GraphQLIncludable
|
4
|
+
module Relay
|
5
|
+
class Instrumentation
|
6
|
+
# rubocop:disable Metrics/AbcSize
|
7
|
+
def instrument(_type, field)
|
8
|
+
return field unless edge_with_node_connection?(field)
|
9
|
+
|
10
|
+
raise ArgumentError, 'Connection does not support fetching using :property' if field.property.present?
|
11
|
+
|
12
|
+
is_proc_based = proc_based?(field)
|
13
|
+
|
14
|
+
validate!(field, is_proc_based)
|
15
|
+
properties = field.metadata[:connection_properties]
|
16
|
+
edge_to_node_property = properties[:edge_to_node]
|
17
|
+
edges_prop = properties[:edges]
|
18
|
+
nodes_prop = properties[:nodes]
|
19
|
+
|
20
|
+
if is_proc_based
|
21
|
+
edges_resolver = field.metadata[:resolve_edges]
|
22
|
+
nodes_resolver = field.metadata[:resolve_nodes]
|
23
|
+
else
|
24
|
+
# Use the edges and nodes symbols from the incldues pattern as the propeties to fetch
|
25
|
+
edges_resolver = ->(obj, _args, _ctx) { obj.public_send(edges_prop) }
|
26
|
+
nodes_resolver = ->(obj, _args, _ctx) { obj.public_send(nodes_prop) }
|
27
|
+
end
|
28
|
+
|
29
|
+
_original_resolve = field.resolve_proc
|
30
|
+
new_resolve_proc = ->(obj, args, ctx) do
|
31
|
+
ConnectionEdgesAndNodes.new(obj, args, ctx,
|
32
|
+
edges_prop, nodes_prop, edge_to_node_property,
|
33
|
+
edges_resolver, nodes_resolver)
|
34
|
+
end
|
35
|
+
|
36
|
+
field.redefine { resolve(new_resolve_proc) }
|
37
|
+
end
|
38
|
+
# rubocop:enable Metrics/AbcSize
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def edge_with_node_connection?(field)
|
43
|
+
field.connection? && field.type.fields['edges'].metadata.key?(:_includable_connection_marker)
|
44
|
+
end
|
45
|
+
|
46
|
+
def proc_based?(field)
|
47
|
+
required_metadata = [:resolve_edges, :resolve_nodes]
|
48
|
+
has_a_resolver = required_metadata.any? { |key| field.metadata.key?(key) }
|
49
|
+
|
50
|
+
return false unless has_a_resolver
|
51
|
+
unless required_metadata.all? { |key| field.metadata.key?(key) }
|
52
|
+
raise ArgumentError, "Missing one of #{required_metadata}"
|
53
|
+
end
|
54
|
+
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate!(field, is_proc_based)
|
59
|
+
unless field.metadata.key?(:connection_properties)
|
60
|
+
raise ArgumentError, 'Missing connection_properties definition for field'
|
61
|
+
end
|
62
|
+
properties = field.metadata[:connection_properties]
|
63
|
+
unless properties.is_a?(Hash)
|
64
|
+
raise ArgumentError, 'Connection includes must be a hash containing :edges and :nodes keys'
|
65
|
+
end
|
66
|
+
raise ArgumentError, 'Missing :nodes' unless is_proc_based || properties.key?(:nodes)
|
67
|
+
raise ArgumentError, 'Missing :edges' unless is_proc_based || properties.key?(:edges)
|
68
|
+
raise ArgumentError, 'Missing :edge_to_node' unless properties.key?(:edge_to_node)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
GraphQL::Relay::BaseConnection.register_connection_implementation(
|
73
|
+
GraphQLIncludable::Relay::ConnectionEdgesAndNodes,
|
74
|
+
GraphQLIncludable::Relay::EdgeWithNodeConnection
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|