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
@@ -1,92 +1,51 @@
|
|
1
|
+
GraphQL::Field.accepts_definitions(
|
2
|
+
##
|
3
|
+
# Define Active Record includes for a field
|
4
|
+
includes: GraphQL::Define.assign_metadata_key(:includes)
|
5
|
+
)
|
6
|
+
|
1
7
|
module GraphQLIncludable
|
2
|
-
class
|
3
|
-
def initialize(
|
4
|
-
@
|
5
|
-
@included_children = {}
|
8
|
+
class Resolver
|
9
|
+
def initialize(ctx)
|
10
|
+
@root_ctx = ctx
|
6
11
|
end
|
7
12
|
|
8
|
-
def
|
9
|
-
return
|
10
|
-
|
11
|
-
manager = IncludesManager.new(association.name)
|
12
|
-
@included_children[association.name] = manager
|
13
|
-
manager
|
14
|
-
end
|
13
|
+
def includes_for_node(node, includes)
|
14
|
+
return includes_for_top_level_connection(node, includes) if node.definition.connection?
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
children = node.scoped_children[node.return_type.unwrap]
|
17
|
+
children.each_value do |child_node|
|
18
|
+
definition_override = node_definition_override(node, child_node)
|
19
|
+
includes_for_child(child_node, includes, definition_override)
|
20
|
+
end
|
18
21
|
end
|
19
22
|
|
20
|
-
|
21
|
-
child_includes = {}
|
22
|
-
child_includes_arr = []
|
23
|
-
@included_children.each do |key, value|
|
24
|
-
if value.empty?
|
25
|
-
child_includes_arr << key
|
26
|
-
else
|
27
|
-
includes = value.includes
|
28
|
-
if includes.is_a?(Array)
|
29
|
-
child_includes_arr += includes
|
30
|
-
else
|
31
|
-
child_includes.merge!(includes)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
23
|
+
private
|
35
24
|
|
36
|
-
|
37
|
-
|
38
|
-
child_includes = child_includes_arr
|
39
|
-
end
|
25
|
+
def includes_for_child(node, includes, definition_override)
|
26
|
+
return includes_for_connection(node, includes, definition_override) if node.definition.connection?
|
40
27
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
28
|
+
builder = build_includes(node, definition_override)
|
29
|
+
return unless builder.present?
|
30
|
+
includes.merge_includes(builder.includes) unless builder.includes.empty?
|
45
31
|
|
46
|
-
|
47
|
-
# Returns the first node in the tree which returns a specific type
|
48
|
-
def self.find_node_by_return_type(node, desired_return_type)
|
49
|
-
return_type = node.return_type.unwrap.to_s
|
50
|
-
return node if return_type == desired_return_type
|
51
|
-
return nil unless node.respond_to?(:scoped_children)
|
52
|
-
|
53
|
-
matching_node = nil
|
54
|
-
node.scoped_children.values.each do |selections|
|
55
|
-
matching_node = selections.values.find do |child_node|
|
56
|
-
find_node_by_return_type(child_node, desired_return_type)
|
57
|
-
end
|
58
|
-
break if matching_node
|
59
|
-
end
|
60
|
-
matching_node
|
61
|
-
end
|
32
|
+
return unless builder.includes?
|
62
33
|
|
63
|
-
|
64
|
-
|
65
|
-
# Noop on nodes that don't return AR (so no associations to include)
|
66
|
-
def self.includes_for_node(node, includes_manager)
|
67
|
-
return_model = node_return_model(node)
|
68
|
-
return [] if return_model.blank?
|
34
|
+
# Determine which [nested] child Includes manager to send to the children
|
35
|
+
child_includes = includes.dig(builder.included_path)
|
69
36
|
|
70
37
|
children = node.scoped_children[node.return_type.unwrap]
|
71
|
-
|
72
38
|
children.each_value do |child_node|
|
73
|
-
|
39
|
+
definition_override = node_definition_override(node, child_node)
|
40
|
+
includes_for_child(child_node, child_includes, definition_override)
|
74
41
|
end
|
75
42
|
end
|
76
43
|
|
77
44
|
# rubocop:disable Metrics/AbcSize
|
78
45
|
# rubocop:disable Metrics/MethodLength
|
79
|
-
def
|
80
|
-
|
81
|
-
return unless
|
82
|
-
|
83
|
-
edges_association = associations_from_parent_model[:edges]
|
84
|
-
nodes_association = associations_from_parent_model[:nodes]
|
85
|
-
|
86
|
-
edge_node_attribute = node.definition.metadata[:edge_to_node_property]
|
87
|
-
edge_model = edges_association.klass
|
88
|
-
edge_to_node_association = edge_model.reflect_on_association(edge_node_attribute)
|
89
|
-
node_model = edge_to_node_association.klass
|
46
|
+
def includes_for_connection(node, includes, definition_override)
|
47
|
+
builder = build_connection_includes(node, definition_override)
|
48
|
+
return unless builder&.includes?
|
90
49
|
|
91
50
|
connection_children = node.scoped_children[node.return_type.unwrap]
|
92
51
|
connection_children.each_value do |connection_node|
|
@@ -105,109 +64,138 @@ module GraphQLIncludable
|
|
105
64
|
# }
|
106
65
|
|
107
66
|
if connection_node.name == 'edges'
|
108
|
-
|
67
|
+
edges_includes_builder = builder.edges_builder.builder
|
68
|
+
includes.merge_includes(edges_includes_builder.includes)
|
69
|
+
edges_includes = edges_includes_builder.path_leaf_includes
|
109
70
|
|
110
71
|
edge_children = connection_node.scoped_children[connection_node.return_type.unwrap]
|
111
72
|
edge_children.each_value do |edge_child_node|
|
112
73
|
if edge_child_node.name == 'node'
|
113
|
-
|
74
|
+
node_includes_builder = builder.edges_builder.node_builder
|
75
|
+
edges_includes.merge_includes(node_includes_builder.includes)
|
76
|
+
edge_node_includes = node_includes_builder.path_leaf_includes
|
114
77
|
|
115
78
|
node_children = edge_child_node.scoped_children[edge_child_node.return_type.unwrap]
|
116
79
|
node_children.each_value do |node_child_node|
|
117
|
-
|
80
|
+
definition_override = node_definition_override(edge_child_node, node_child_node)
|
81
|
+
includes_for_child(node_child_node, edge_node_includes, definition_override)
|
118
82
|
end
|
119
83
|
else
|
120
|
-
|
84
|
+
definition_override = node_definition_override(connection_node, edge_child_node)
|
85
|
+
includes_for_child(edge_child_node, edges_includes, definition_override)
|
121
86
|
end
|
122
87
|
end
|
123
88
|
elsif connection_node.name == 'nodes'
|
124
|
-
|
89
|
+
nodes_includes_builder = builder.nodes_builder
|
90
|
+
includes.merge_includes(nodes_includes_builder.includes)
|
91
|
+
nodes_includes = nodes_includes_builder.path_leaf_includes
|
92
|
+
|
125
93
|
node_children = connection_node.scoped_children[connection_node.return_type.unwrap]
|
126
94
|
node_children.each_value do |node_child_node|
|
127
|
-
|
95
|
+
definition_override = node_definition_override(connection_node, node_child_node)
|
96
|
+
includes_for_child(node_child_node, nodes_includes, definition_override)
|
128
97
|
end
|
129
98
|
elsif connection_node.name == 'totalCount'
|
130
|
-
# Handled using `.size`
|
99
|
+
# Handled using `.size`
|
131
100
|
end
|
132
101
|
end
|
133
102
|
end
|
134
|
-
# rubocop:enable Metrics/MethodLength
|
135
|
-
# rubocop:enable Metrics/AbcSize
|
136
|
-
|
137
|
-
def self.includes_for_child(node, parent_model, includes_manager)
|
138
|
-
associations = possible_associations(node, parent_model)
|
139
|
-
return unless associations.present?
|
140
103
|
|
141
|
-
|
142
|
-
|
104
|
+
# Special case:
|
105
|
+
# When includes_for_node is called within a connection resolver, there is no need to use that field's nodes/edges
|
106
|
+
# includes, only edge_to_node includes
|
107
|
+
def includes_for_top_level_connection(node, includes)
|
108
|
+
connection_children = node.scoped_children[node.return_type.unwrap]
|
109
|
+
top_level_being_resolved = @root_ctx.namespace(:gql_includable)[:resolving]
|
110
|
+
|
111
|
+
if top_level_being_resolved == :edges
|
112
|
+
builder = build_connection_includes(node, nil)
|
113
|
+
return unless builder&.edges_builder&.node_builder&.includes?
|
114
|
+
|
115
|
+
edges_node = connection_children['edges']
|
116
|
+
edges_includes = includes
|
117
|
+
|
118
|
+
edge_children = edges_node.scoped_children[edges_node.return_type.unwrap]
|
119
|
+
edge_children.each_value do |edge_child_node|
|
120
|
+
if edge_child_node.name == 'node'
|
121
|
+
node_includes_builder = builder.edges_builder.node_builder
|
122
|
+
edges_includes.merge_includes(node_includes_builder.includes)
|
123
|
+
edge_node_includes = node_includes_builder.path_leaf_includes
|
124
|
+
|
125
|
+
node_children = edge_child_node.scoped_children[edge_child_node.return_type.unwrap]
|
126
|
+
node_children.each_value do |node_child_node|
|
127
|
+
definition_override = node_definition_override(edge_child_node, node_child_node)
|
128
|
+
includes_for_child(node_child_node, edge_node_includes, definition_override)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
definition_override = node_definition_override(edges_node, edge_child_node)
|
132
|
+
includes_for_child(edge_child_node, edges_includes, definition_override)
|
133
|
+
end
|
134
|
+
end
|
143
135
|
else
|
144
|
-
|
145
|
-
|
146
|
-
|
136
|
+
nodes_node = connection_children['nodes']
|
137
|
+
return unless nodes_node.present?
|
138
|
+
nodes_includes = includes
|
139
|
+
|
140
|
+
node_children = nodes_node.scoped_children[nodes_node.return_type.unwrap]
|
141
|
+
node_children.each_value do |node_child_node|
|
142
|
+
definition_override = node_definition_override(nodes_node, node_child_node)
|
143
|
+
includes_for_child(node_child_node, nodes_includes, definition_override)
|
144
|
+
end
|
147
145
|
end
|
148
146
|
end
|
147
|
+
# rubocop:enable Metrics/MethodLength
|
148
|
+
# rubocop:enable Metrics/AbcSize
|
149
149
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
rescue NameError
|
155
|
-
model_name.to_s.singularize.camelize.constantize
|
156
|
-
end
|
157
|
-
rescue
|
158
|
-
end
|
150
|
+
def build_includes(node, definition_override)
|
151
|
+
definition = definition_override || node.definition
|
152
|
+
includes_meta = definition.metadata[:includes]
|
153
|
+
return nil if includes_meta.blank?
|
159
154
|
|
160
|
-
|
161
|
-
def self.node_return_model(node)
|
162
|
-
model = Object.const_get(node.return_type.unwrap.name.gsub(/(^SquareFoot|Edge$|Connection$)/, ''))
|
163
|
-
model if model < ActiveRecord::Base
|
164
|
-
rescue NameError
|
165
|
-
end
|
166
|
-
# rubocop:enable Lint/HandleExceptions
|
155
|
+
builder = GraphQLIncludable::IncludesBuilder.new
|
167
156
|
|
168
|
-
|
169
|
-
|
170
|
-
|
157
|
+
if includes_meta.is_a?(Proc)
|
158
|
+
if includes_meta.arity == 2
|
159
|
+
args_for_field = @root_ctx.query.arguments_for(node, node.definition)
|
160
|
+
builder.instance_exec(args_for_field, @root_ctx, &includes_meta)
|
161
|
+
else
|
162
|
+
builder.instance_exec(&includes_meta)
|
163
|
+
end
|
164
|
+
else
|
165
|
+
builder.path(includes_meta)
|
166
|
+
end
|
171
167
|
|
172
|
-
|
173
|
-
attribute_names = node_possible_association_names(node)
|
174
|
-
attribute_names.transform_values { |attribute_name| figure_out_association(attribute_name, parent_model) }.compact
|
168
|
+
builder
|
175
169
|
end
|
176
170
|
|
177
|
-
def
|
178
|
-
definition = node.
|
179
|
-
|
171
|
+
def build_connection_includes(node, definition_override)
|
172
|
+
definition = definition_override || node.definition
|
173
|
+
includes_meta = definition.metadata[:includes]
|
174
|
+
return nil if includes_meta.blank?
|
180
175
|
|
181
|
-
|
182
|
-
if
|
183
|
-
|
184
|
-
|
185
|
-
|
176
|
+
builder = GraphQLIncludable::ConnectionIncludesBuilder.new
|
177
|
+
if includes_meta.arity == 2
|
178
|
+
args_for_field = @root_ctx.query.arguments_for(node, node.definition)
|
179
|
+
builder.instance_exec(args_for_field, @root_ctx, &includes_meta)
|
180
|
+
else
|
181
|
+
builder.instance_exec(&includes_meta)
|
186
182
|
end
|
187
|
-
|
188
|
-
association_names[:default] = user_includes || (definition.property || definition.name).to_sym
|
189
|
-
association_names
|
183
|
+
builder
|
190
184
|
end
|
191
185
|
|
192
|
-
def
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
while model_name
|
206
|
-
chain << model_name
|
207
|
-
model = model_name_to_class(model_name)
|
208
|
-
model_name = model.instance_variable_get(:@delegate_cache).try(:[], method)
|
209
|
-
end
|
210
|
-
chain
|
186
|
+
def node_definition_override(parent_node, child_node)
|
187
|
+
node_return_type = parent_node.return_type.unwrap
|
188
|
+
child_node_parent_type = child_node.parent.return_type.unwrap
|
189
|
+
|
190
|
+
return nil unless child_node_parent_type != node_return_type
|
191
|
+
child_node_definition_override = nil
|
192
|
+
# Handle GraphQL interface with overridden fields
|
193
|
+
# GraphQL makes child_node.return_type the interface instance
|
194
|
+
# and therefore takes the metadata from the interface rather than the
|
195
|
+
# implementing object's overridden field instance
|
196
|
+
is_interface = node_return_type.interfaces.include?(child_node_parent_type)
|
197
|
+
child_node_definition_override = node_return_type.fields[child_node.name] if is_interface
|
198
|
+
child_node_definition_override
|
211
199
|
end
|
212
200
|
end
|
213
201
|
end
|
data/lib/graphql_includable.rb
CHANGED
@@ -1,33 +1,41 @@
|
|
1
1
|
require 'graphql'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
require 'graphql_includable/relay/edge_with_node_connection_type'
|
8
|
-
require 'graphql_includable/relay/instrumentation/connection'
|
9
|
-
require 'graphql_includable/new/gql_includable'
|
2
|
+
require_relative 'graphql_includable/includes_builder'
|
3
|
+
require_relative 'graphql_includable/includes'
|
4
|
+
require_relative 'graphql_includable/resolver'
|
5
|
+
require_relative 'graphql_includable/relay/edge_with_node_connection_type'
|
6
|
+
require_relative 'graphql_includable/relay/instrumentation'
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
_includable_connection_marker: GraphQL::Define.assign_metadata_key(:_includable_connection_marker)
|
18
|
-
)
|
8
|
+
module GraphQLIncludable
|
9
|
+
def self.includes(ctx, starting_at: nil)
|
10
|
+
ActiveSupport::Notifications.instrument('graphql_includable.includes') do |instrument|
|
11
|
+
instrument[:operation_name] = ctx.query&.operation_name
|
12
|
+
instrument[:field_name] = ctx.irep_node.name
|
13
|
+
instrument[:starting_at] = starting_at
|
19
14
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
15
|
+
includes = Includes.new(nil)
|
16
|
+
resolver = Resolver.new(ctx)
|
17
|
+
|
18
|
+
node = ctx.irep_node
|
19
|
+
node = node_for_path(node, starting_at) if starting_at.present?
|
20
|
+
|
21
|
+
generated_includes = if node.present?
|
22
|
+
resolver.includes_for_node(node, includes)
|
23
|
+
includes.active_record_includes
|
24
|
+
end
|
25
|
+
|
26
|
+
instrument[:includes] = generated_includes
|
27
|
+
generated_includes
|
29
28
|
end
|
29
|
+
end
|
30
30
|
|
31
|
+
def self.node_for_path(node, path_into_query)
|
32
|
+
children = node.scoped_children[node.return_type.unwrap]&.with_indifferent_access || {}
|
33
|
+
children.dig(path_into_query)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module GraphQL
|
38
|
+
class BaseType
|
31
39
|
def define_connection_with_fetched_edge(**kwargs, &block)
|
32
40
|
GraphQLIncludable::Relay::EdgeWithNodeConnectionType.create_type(
|
33
41
|
self,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphql_includable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.beta.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Rouse
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2019-
|
13
|
+
date: 2019-10-04 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description:
|
16
16
|
email:
|
@@ -22,20 +22,12 @@ extensions: []
|
|
22
22
|
extra_rdoc_files: []
|
23
23
|
files:
|
24
24
|
- lib/graphql_includable.rb
|
25
|
-
- lib/graphql_includable/
|
26
|
-
- lib/graphql_includable/
|
27
|
-
- lib/graphql_includable/new/gql_includable.rb
|
28
|
-
- lib/graphql_includable/new/includes.rb
|
29
|
-
- lib/graphql_includable/new/includes_builder.rb
|
30
|
-
- lib/graphql_includable/new/relay/edge_with_node.rb
|
31
|
-
- lib/graphql_includable/new/relay/edge_with_node_connection.rb
|
32
|
-
- lib/graphql_includable/new/relay/edge_with_node_connection_type.rb
|
33
|
-
- lib/graphql_includable/new/relay/instrumentation.rb
|
34
|
-
- lib/graphql_includable/new/resolver.rb
|
25
|
+
- lib/graphql_includable/includes.rb
|
26
|
+
- lib/graphql_includable/includes_builder.rb
|
35
27
|
- lib/graphql_includable/relay/edge_with_node.rb
|
36
28
|
- lib/graphql_includable/relay/edge_with_node_connection.rb
|
37
29
|
- lib/graphql_includable/relay/edge_with_node_connection_type.rb
|
38
|
-
- lib/graphql_includable/relay/instrumentation
|
30
|
+
- lib/graphql_includable/relay/instrumentation.rb
|
39
31
|
- lib/graphql_includable/resolver.rb
|
40
32
|
homepage: https://github.com/thesquarefoot/graphql_includable
|
41
33
|
licenses:
|
@@ -52,9 +44,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
44
|
version: '0'
|
53
45
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
46
|
requirements:
|
55
|
-
- - "
|
47
|
+
- - ">"
|
56
48
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
49
|
+
version: 1.3.1
|
58
50
|
requirements: []
|
59
51
|
rubyforge_project:
|
60
52
|
rubygems_version: 2.6.14.1
|
@@ -1,39 +0,0 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
|
3
|
-
module GraphQLIncludable
|
4
|
-
module Concern
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
module ClassMethods
|
8
|
-
def includes_from_graphql(ctx)
|
9
|
-
warn '[DEPRECATION] `includes_from_graphql` is deprecated. See migration guide in README.'
|
10
|
-
ActiveSupport::Notifications.instrument('graphql_includable.includes_from_graphql') do |instrument|
|
11
|
-
instrument[:operation_name] = ctx.query&.operation_name
|
12
|
-
instrument[:field_name] = ctx.irep_node.name
|
13
|
-
|
14
|
-
node = Resolver.find_node_by_return_type(ctx.irep_node, name)
|
15
|
-
manager = IncludesManager.new(nil)
|
16
|
-
Resolver.includes_for_node(node, manager)
|
17
|
-
|
18
|
-
generated_includes = manager.includes
|
19
|
-
instrument[:includes] = generated_includes
|
20
|
-
includes(generated_includes)
|
21
|
-
end
|
22
|
-
rescue => e
|
23
|
-
Rails.logger.error(e)
|
24
|
-
self
|
25
|
-
end
|
26
|
-
|
27
|
-
def delegate_cache
|
28
|
-
@delegate_cache ||= {}
|
29
|
-
end
|
30
|
-
|
31
|
-
def delegate(*methods, args)
|
32
|
-
methods.each do |method|
|
33
|
-
delegate_cache[method] = args[:to]
|
34
|
-
end
|
35
|
-
super(*methods, args) if defined?(super)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,108 +0,0 @@
|
|
1
|
-
# rubocop:disable Style/ConditionalAssignment
|
2
|
-
# rubocop:disable Lint/HandleExceptions
|
3
|
-
# rubocop:disable Metrics/AbcSize
|
4
|
-
# rubocop:disable Style/IfInsideElse
|
5
|
-
# rubocop:disable Metrics/MethodLength
|
6
|
-
module GraphQLIncludable
|
7
|
-
class Edge < GraphQL::Relay::Edge
|
8
|
-
def edge
|
9
|
-
return @edge if @edge
|
10
|
-
|
11
|
-
join_chain = joins_along_edge
|
12
|
-
edge_class_name = join_chain.shift
|
13
|
-
edge_class = str_to_class(edge_class_name)
|
14
|
-
|
15
|
-
parent = if self.parent.instance_of?(GraphQLIncludable::New::Relay::LazyEvaluatedNode)
|
16
|
-
parent_class = self.parent._value.class
|
17
|
-
self.parent._value
|
18
|
-
else
|
19
|
-
parent_class = self.parent.class
|
20
|
-
self.parent
|
21
|
-
end
|
22
|
-
|
23
|
-
root_association_key = class_to_str(parent_class)
|
24
|
-
unless edge_class.reflections.keys.include?(root_association_key)
|
25
|
-
is_polymorphic = true
|
26
|
-
root_association_key = edge_class.reflections.select { |_k, r| r.polymorphic? }.keys.first
|
27
|
-
end
|
28
|
-
|
29
|
-
if parent_class.delegate_cache&.key?(edge_class_name)
|
30
|
-
root_association_search_value = parent.send(parent_class.delegate_cache[edge_class_name])
|
31
|
-
else
|
32
|
-
root_association_search_value = parent
|
33
|
-
end
|
34
|
-
|
35
|
-
root_node = { root_association_key.to_sym => [root_association_search_value] }
|
36
|
-
terminal_node = { class_to_str(node.class) => node }
|
37
|
-
join_chain.reverse.each do |rel_name|
|
38
|
-
terminal_node = { rel_name.to_s.pluralize => terminal_node }
|
39
|
-
end
|
40
|
-
|
41
|
-
search_hash = root_node.merge(terminal_node)
|
42
|
-
edge_includes = join_chain.map { |s| s.to_s.singularize }
|
43
|
-
edge_class = edge_class.includes(*edge_includes) unless edge_includes.empty?
|
44
|
-
edge_class = edge_class.joins(root_association_key.to_sym) unless is_polymorphic
|
45
|
-
@edge ||= edge_class.find_by(search_hash)
|
46
|
-
end
|
47
|
-
|
48
|
-
def method_missing(method_name, *args, &block)
|
49
|
-
if edge.respond_to?(method_name)
|
50
|
-
edge.send(method_name, *args, &block)
|
51
|
-
else
|
52
|
-
super
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def respond_to_missing?(method_name, include_private = false)
|
57
|
-
edge.respond_to?(method_name) || super
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def str_to_class(str)
|
63
|
-
str.to_s.singularize.camelize.constantize
|
64
|
-
rescue
|
65
|
-
end
|
66
|
-
|
67
|
-
def class_to_str(klass)
|
68
|
-
klass.name.downcase
|
69
|
-
end
|
70
|
-
|
71
|
-
def joins_along_edge
|
72
|
-
# node.edges
|
73
|
-
# edge.node
|
74
|
-
# edge.parent
|
75
|
-
# parent.edges
|
76
|
-
# node.parents
|
77
|
-
# parent.nodes
|
78
|
-
edge_association_name = node.class.name.pluralize.downcase.to_sym
|
79
|
-
if parent.instance_of?(GraphQLIncludable::New::Relay::LazyEvaluatedNode)
|
80
|
-
parent_class = parent._value.class
|
81
|
-
else
|
82
|
-
if parent.class.delegate_cache&.key?(edge_association_name)
|
83
|
-
parent_class = str_to_class(parent.class.delegate_cache[edge_association_name])
|
84
|
-
else
|
85
|
-
parent_class = parent.class
|
86
|
-
end
|
87
|
-
end
|
88
|
-
edge_association = parent_class.reflect_on_association(edge_association_name)
|
89
|
-
edge_joins = []
|
90
|
-
while edge_association.is_a? ActiveRecord::Reflection::ThroughReflection
|
91
|
-
edge_joins.unshift edge_association.options[:through]
|
92
|
-
edge_association = parent_class.reflect_on_association(edge_association.options[:through])
|
93
|
-
end
|
94
|
-
edge_joins
|
95
|
-
# join_chain = []
|
96
|
-
# starting_class = parent.class
|
97
|
-
# node_relationship_name = class_to_str(node.class)
|
98
|
-
# while starting_class
|
99
|
-
# reflection = starting_class.reflect_on_association(node_relationship_name)
|
100
|
-
# association_name = reflection&.options&.try(:[], :through)
|
101
|
-
# join_chain << association_name if association_name
|
102
|
-
# starting_class = str_to_class(association_name)
|
103
|
-
# node_relationship_name = node_relationship_name.singularize
|
104
|
-
# end
|
105
|
-
# join_chain
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
require 'graphql'
|
2
|
-
require_relative 'includes_builder'
|
3
|
-
require_relative 'includes'
|
4
|
-
require_relative 'resolver'
|
5
|
-
require_relative 'relay/edge_with_node_connection_type'
|
6
|
-
require_relative 'relay/instrumentation'
|
7
|
-
|
8
|
-
module GraphQLIncludable
|
9
|
-
module New
|
10
|
-
def self.includes(ctx, starting_at: nil)
|
11
|
-
ActiveSupport::Notifications.instrument('graphql_includable.includes') do |instrument|
|
12
|
-
instrument[:operation_name] = ctx.query&.operation_name
|
13
|
-
instrument[:field_name] = ctx.irep_node.name
|
14
|
-
instrument[:starting_at] = starting_at
|
15
|
-
|
16
|
-
includes = Includes.new(nil)
|
17
|
-
resolver = Resolver.new(ctx)
|
18
|
-
|
19
|
-
node = ctx.irep_node
|
20
|
-
node = node_for_path(node, starting_at) if starting_at.present?
|
21
|
-
|
22
|
-
generated_includes = if node.present?
|
23
|
-
resolver.includes_for_node(node, includes)
|
24
|
-
includes.active_record_includes
|
25
|
-
end
|
26
|
-
|
27
|
-
instrument[:includes] = generated_includes
|
28
|
-
generated_includes
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.node_for_path(node, path_into_query)
|
33
|
-
children = node.scoped_children[node.return_type.unwrap]&.with_indifferent_access || {}
|
34
|
-
children.dig(path_into_query)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
module GraphQL
|
40
|
-
class BaseType
|
41
|
-
def new_define_connection_with_fetched_edge(**kwargs, &block)
|
42
|
-
GraphQLIncludable::New::Relay::EdgeWithNodeConnectionType.create_type(
|
43
|
-
self,
|
44
|
-
**kwargs,
|
45
|
-
&block
|
46
|
-
)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|