graphql_includable 0.4.0 → 0.5.0.beta.1

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: c26fc967e743b93dc24703e364dc4500f17d94b9
4
- data.tar.gz: 2c63040e1ed7695b36c9911fd44f6892d9c48c8f
3
+ metadata.gz: 21738d44c97c02d307c624f23191471f5701e078
4
+ data.tar.gz: acacb75291ee29bc5356b577ba2df1e9933ae8ca
5
5
  SHA512:
6
- metadata.gz: 7fda91af53b2e1057d0eb3acdaa251bda2b96b394f69445c309fe388a79ef6b0f4db76e694af58083b64676d5ced3a291fcbd82a3ad62064025637d08cd09e7d
7
- data.tar.gz: 294fcbba508907345fc91a672e55a8305ff9702a89b0b34cbb0f397dbf64b15ef20fcb230c11ecf72281a6e12db4b7aafdcab0a5be554868e2410dee22844ba4
6
+ metadata.gz: 0d63dae811fa795631ab1fc7a996421c7113f72c97151fadd17dc1e76c3c9ce8ca61c8262fde791105cadd2bfb6281a2f4575d6b9645c4e09bc1648d66bd8638
7
+ data.tar.gz: da84988546493f2decef19b3b8bc92b1d02c27a1876c85b59b49e7d650d8e960500b21e85272f3dd996a9aa798e00b72d71db64062a40bc70dbd1f0b26cdd881
@@ -6,10 +6,18 @@ module GraphQLIncludable
6
6
 
7
7
  module ClassMethods
8
8
  def includes_from_graphql(ctx)
9
- node = Resolver.find_node_by_return_type(ctx.irep_node, name)
10
- manager = IncludesManager.new(nil)
11
- Resolver.includes_for_node(node, manager)
12
- includes(manager.includes)
9
+ ActiveSupport::Notifications.instrument('graphql_includable.includes_from_graphql') do |instrument|
10
+ instrument[:operation_name] = ctx.query&.operation_name
11
+ instrument[:field_name] = ctx.irep_node.name
12
+
13
+ node = Resolver.find_node_by_return_type(ctx.irep_node, name)
14
+ manager = IncludesManager.new(nil)
15
+ Resolver.includes_for_node(node, manager)
16
+
17
+ generated_includes = manager.includes
18
+ instrument[:includes] = generated_includes
19
+ includes(generated_includes)
20
+ end
13
21
  rescue => e
14
22
  Rails.logger.error(e)
15
23
  self
@@ -1,22 +1,33 @@
1
1
  # rubocop:disable Style/ConditionalAssignment
2
2
  # rubocop:disable Lint/HandleExceptions
3
3
  # rubocop:disable Metrics/AbcSize
4
+ # rubocop:disable Style/IfInsideElse
5
+ # rubocop:disable Metrics/MethodLength
4
6
  module GraphQLIncludable
5
7
  class Edge < GraphQL::Relay::Edge
6
8
  def edge
7
9
  return @edge if @edge
10
+
8
11
  join_chain = joins_along_edge
9
12
  edge_class_name = join_chain.shift
10
13
  edge_class = str_to_class(edge_class_name)
11
14
 
12
- root_association_key = class_to_str(parent.class)
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)
13
24
  unless edge_class.reflections.keys.include?(root_association_key)
14
25
  is_polymorphic = true
15
26
  root_association_key = edge_class.reflections.select { |_k, r| r.polymorphic? }.keys.first
16
27
  end
17
28
 
18
- if parent.class.delegate_cache&.key?(edge_class_name)
19
- root_association_search_value = parent.send(parent.class.delegate_cache[edge_class_name])
29
+ if parent_class.delegate_cache&.key?(edge_class_name)
30
+ root_association_search_value = parent.send(parent_class.delegate_cache[edge_class_name])
20
31
  else
21
32
  root_association_search_value = parent
22
33
  end
@@ -65,10 +76,14 @@ module GraphQLIncludable
65
76
  # node.parents
66
77
  # parent.nodes
67
78
  edge_association_name = node.class.name.pluralize.downcase.to_sym
68
- if parent.class.delegate_cache&.key?(edge_association_name)
69
- parent_class = str_to_class(parent.class.delegate_cache[edge_association_name])
79
+ if parent.instance_of?(GraphQLIncludable::New::Relay::LazyEvaluatedNode)
80
+ parent_class = parent._value.class
70
81
  else
71
- parent_class = parent.class
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
72
87
  end
73
88
  edge_association = parent_class.reflect_on_association(edge_association_name)
74
89
  edge_joins = []
@@ -0,0 +1,36 @@
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)
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
+
15
+ includes = Includes.new(nil)
16
+ resolver = Resolver.new(ctx)
17
+ resolver.includes_for_node(ctx.irep_node, includes)
18
+ generated_includes = includes.active_record_includes
19
+ instrument[:includes] = generated_includes
20
+ generated_includes
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ module GraphQL
27
+ class BaseType
28
+ def new_define_connection_with_fetched_edge(**kwargs, &block)
29
+ GraphQLIncludable::New::Relay::EdgeWithNodeConnectionType.create_type(
30
+ self,
31
+ **kwargs,
32
+ &block
33
+ )
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,63 @@
1
+ module GraphQLIncludable
2
+ module New
3
+ class Includes
4
+ attr_reader :included_children
5
+
6
+ def initialize(parent_attribute)
7
+ @parent_attribute = parent_attribute
8
+ @included_children = {}
9
+ end
10
+
11
+ def add_child(key)
12
+ return @included_children[key] if @included_children.key?(key)
13
+ manager = Includes.new(key)
14
+ @included_children[key] = manager
15
+ manager
16
+ end
17
+
18
+ def merge_includes(includes_manager)
19
+ includes_manager.included_children.each do |key, manager|
20
+ included_children[key] = if included_children.key?(key)
21
+ included_children[key].merge_includes(manager)
22
+ else
23
+ manager
24
+ end
25
+ end
26
+ self
27
+ end
28
+
29
+ def [](key)
30
+ @included_children[key]
31
+ end
32
+
33
+ def empty?
34
+ @included_children.empty?
35
+ end
36
+
37
+ def active_record_includes
38
+ child_includes = {}
39
+ child_includes_arr = []
40
+ @included_children.each do |key, value|
41
+ if value.empty?
42
+ child_includes_arr << key
43
+ else
44
+ active_record_includes = value.active_record_includes
45
+ if active_record_includes.is_a?(Array)
46
+ child_includes_arr += active_record_includes
47
+ else
48
+ child_includes.merge!(active_record_includes)
49
+ end
50
+ end
51
+ end
52
+
53
+ if child_includes_arr.present?
54
+ child_includes_arr << child_includes if child_includes.present?
55
+ child_includes = child_includes_arr
56
+ end
57
+
58
+ return child_includes if @parent_attribute.nil?
59
+ { @parent_attribute => child_includes }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,114 @@
1
+ module GraphQLIncludable
2
+ module New
3
+ class IncludesBuilder
4
+ attr_reader :included_path, :includes
5
+
6
+ def initialize(only_one_path: true)
7
+ @only_one_path = only_one_path
8
+ @included_path = []
9
+ @includes = GraphQLIncludable::New::Includes.new(nil)
10
+ end
11
+
12
+ def includes?
13
+ @included_path.present?
14
+ end
15
+
16
+ def active_record_includes
17
+ @includes.active_record_includes
18
+ end
19
+
20
+ def path_leaf_includes
21
+ leaf_includes = @includes
22
+ included_path.each do |key|
23
+ leaf_includes = leaf_includes[key]
24
+ end
25
+ leaf_includes
26
+ end
27
+
28
+ def path(*symbols, &block)
29
+ raise ArgumentError, 'Can only add path once' if @included_path.present? && @only_one_path
30
+
31
+ if symbols.present?
32
+ first, *rest = symbols
33
+ includes = @includes.add_child(first)
34
+ rest.each do |key|
35
+ includes = includes.add_child(key)
36
+ end
37
+ else
38
+ includes = @includes
39
+ end
40
+
41
+ if block_given?
42
+ nested = GraphQLIncludable::New::IncludesBuilder.new
43
+ nested.instance_eval(&block)
44
+ symbols += nested.included_path
45
+ includes.merge_includes(nested.includes)
46
+ end
47
+ @included_path = symbols
48
+ end
49
+
50
+ def sibling_path(*symbols, &block)
51
+ if symbols.present?
52
+ first, *rest = symbols
53
+ includes = @includes.add_child(first)
54
+ rest.each do |key|
55
+ includes = includes.add_child(key)
56
+ end
57
+ else
58
+ includes = @includes
59
+ end
60
+
61
+ return unless block_given?
62
+ nested = GraphQLIncludable::New::IncludesBuilder.new(only_one_path: false)
63
+ nested.instance_eval(&block)
64
+ includes.merge_includes(nested.includes)
65
+ end
66
+ end
67
+
68
+ class ConnectionIncludesBuilder
69
+ attr_reader :nodes_builder, :edges_builder
70
+
71
+ def initialize
72
+ @nodes_builder = IncludesBuilder.new
73
+ @edges_builder = ConnectionEdgesIncludesBuilder.new
74
+ end
75
+
76
+ def includes?
77
+ @nodes_builder.includes? || @edges_builder.includes?
78
+ end
79
+
80
+ def nodes(*symbols, &block)
81
+ @nodes_builder.path(*symbols, &block)
82
+ end
83
+
84
+ def edges(&block)
85
+ @edges_builder.instance_eval(&block)
86
+ end
87
+ end
88
+
89
+ class ConnectionEdgesIncludesBuilder
90
+ attr_reader :builder, :node_builder
91
+
92
+ def initialize
93
+ @builder = IncludesBuilder.new
94
+ @node_builder = IncludesBuilder.new
95
+ end
96
+
97
+ def includes?
98
+ @builder.includes? && @node_builder.includes?
99
+ end
100
+
101
+ def path(*symbols, &block)
102
+ @builder.path(*symbols, &block)
103
+ end
104
+
105
+ def sibling_path(*symbols, &block)
106
+ @builder.sibling_path(*symbols, &block)
107
+ end
108
+
109
+ def node(*symbols, &block)
110
+ @node_builder.path(*symbols, &block)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,48 @@
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
+ _new_includable_connection_marker: GraphQL::Define.assign_metadata_key(:_new_includable_connection_marker)
17
+ )
18
+
19
+ module GraphQLIncludable
20
+ module New
21
+ module Relay
22
+ class EdgeWithNode < GraphQL::Relay::Edge
23
+ def initialize(node, connection)
24
+ @edge = node
25
+ @edge_to_node = ->() { connection.edge_to_node(@edge) }
26
+ super(nil, connection)
27
+ end
28
+
29
+ def node
30
+ @node ||= @edge_to_node.call
31
+ @node
32
+ end
33
+
34
+ def method_missing(method_name, *args, &block)
35
+ if @edge.respond_to?(method_name)
36
+ @edge.send(method_name, *args, &block)
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def respond_to_missing?(method_name, include_private = false)
43
+ @edge.respond_to?(method_name) || super
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,101 @@
1
+ module GraphQLIncludable
2
+ module New
3
+ module Relay
4
+ class ConnectionEdgesAndNodes
5
+ attr_reader :parent, :args, :ctx, :edges_property, :nodes_property, :edge_to_node_property
6
+ attr_reader :edges_resolver, :nodes_resolver
7
+
8
+ # rubocop:disable Metrics/ParameterLists
9
+ def initialize(parent, args, ctx,
10
+ edges_property, nodes_property, edge_to_node_property,
11
+ edges_resolver, nodes_resolver)
12
+ @parent = parent
13
+ @args = args
14
+ @ctx = ctx
15
+ @edges_property = edges_property # optional
16
+ @nodes_property = nodes_property # optional
17
+ @edge_to_node_property = edge_to_node_property
18
+ @edges_resolver = edges_resolver
19
+ @nodes_resolver = nodes_resolver
20
+ end
21
+ # rubocop:enable Metrics/ParameterLists
22
+ end
23
+
24
+ class EdgeWithNodeConnection < GraphQL::Relay::RelationConnection
25
+ def initialize(nodes, *args, &block)
26
+ @edges_and_nodes = nodes
27
+ @loaded_nodes = nil
28
+ @loaded_edges = nil
29
+ super(nil, *args, &block)
30
+ end
31
+
32
+ def edge_nodes
33
+ raise 'This should not be called from a EdgeWithNodeConnectionType'
34
+ end
35
+
36
+ def fetch_edges
37
+ # This context is used within Resolver for connections
38
+ ctx.namespace(:gql_includable)[:resolving] = :edges
39
+ @loaded_edges ||= @edges_and_nodes.edges_resolver.call(@edges_and_nodes.parent, args, ctx)
40
+ ctx.namespace(:gql_includable)[:resolving] = nil
41
+ # Set nodes to make underlying BaseConnection work
42
+ @nodes = @loaded_edges
43
+ @loaded_edges
44
+ end
45
+
46
+ def fetch_nodes
47
+ # This context is used within Resolver for connections
48
+ ctx.namespace(:gql_includable)[:resolving] = :nodes
49
+ @loaded_nodes ||= @edges_and_nodes.nodes_resolver.call(@edges_and_nodes.parent, args, ctx)
50
+ ctx.namespace(:gql_includable)[:resolving] = nil
51
+ # Set nodes to make underlying BaseConnection work
52
+ @nodes = @loaded_nodes
53
+ @loaded_nodes
54
+ end
55
+
56
+ def page_info
57
+ @nodes = determine_page_info_nodes
58
+ super
59
+ end
60
+
61
+ def edge_to_node(edge)
62
+ edge.public_send(@edges_and_nodes.edge_to_node_property)
63
+ end
64
+
65
+ def total_count
66
+ @nodes = determine_page_info_nodes
67
+ @nodes.size
68
+ end
69
+
70
+ private
71
+
72
+ def args
73
+ @edges_and_nodes.args
74
+ end
75
+
76
+ def ctx
77
+ @edges_and_nodes.ctx
78
+ end
79
+
80
+ def determine_page_info_nodes
81
+ # If the query asks for `pageInfo` before `edges` or `nodes`, we dont directly know which to use most
82
+ # efficently. We can have a guess by checking if either of the associations are preloaded
83
+ return @loaded_nodes if @loaded_nodes.present?
84
+ return @loaded_edges if @loaded_edges.present?
85
+
86
+ if @edges_and_nodes.nodes_property.present?
87
+ nodes_preloaded = @edges_and_nodes.parent.association(@edges_and_nodes.nodes_property).loaded?
88
+ return fetch_nodes if nodes_preloaded
89
+ end
90
+
91
+ if @edges_and_nodes.edges_property.present?
92
+ edges_preloaded = @edges_and_nodes.parent.association(@edges_and_nodes.edges_property).loaded?
93
+ return fetch_edges if edges_preloaded
94
+ end
95
+
96
+ fetch_nodes
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'edge_with_node'
2
+
3
+ module GraphQLIncludable
4
+ module New
5
+ module Relay
6
+ class EdgeWithNodeConnectionType
7
+ def self.create_type(
8
+ wrapped_type,
9
+ edge_type: wrapped_type.edge_type, edge_class: EdgeWithNode,
10
+ nodes_field: GraphQL::Relay::ConnectionType.default_nodes_field, &block
11
+ )
12
+ custom_edge_class = edge_class
13
+
14
+ GraphQL::ObjectType.define do
15
+ name("#{wrapped_type.name}Connection")
16
+ description("The connection type for #{wrapped_type.name}.")
17
+
18
+ field :totalCount, types.Int, 'Total count.', property: :total_count
19
+
20
+ field :edges, types[edge_type], 'A list of edges.' do
21
+ edge_class custom_edge_class
22
+ property :fetch_edges
23
+ _new_includable_connection_marker true
24
+ end
25
+
26
+ if nodes_field
27
+ field :nodes, types[wrapped_type], 'A list of nodes.', property: :fetch_nodes
28
+ end
29
+
30
+ field :pageInfo, !GraphQL::Relay::PageInfo, 'Information to aid in pagination.', property: :page_info
31
+ block && instance_eval(&block)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,79 @@
1
+ require_relative 'edge_with_node_connection'
2
+
3
+ module GraphQLIncludable
4
+ module New
5
+ module Relay
6
+ class Instrumentation
7
+ # rubocop:disable Metrics/AbcSize
8
+ def instrument(_type, field)
9
+ return field unless edge_with_node_connection?(field)
10
+
11
+ raise ArgumentError, 'Connection does not support fetching using :property' if field.property.present?
12
+
13
+ is_proc_based = proc_based?(field)
14
+
15
+ validate!(field, is_proc_based)
16
+ properties = field.metadata[:connection_properties]
17
+ edge_to_node_property = properties[:edge_to_node]
18
+ edges_prop = properties[:edges]
19
+ nodes_prop = properties[:nodes]
20
+
21
+ if is_proc_based
22
+ edges_resolver = field.metadata[:resolve_edges]
23
+ nodes_resolver = field.metadata[:resolve_nodes]
24
+ else
25
+ # Use the edges and nodes symbols from the incldues pattern as the propeties to fetch
26
+ edges_resolver = ->(obj, _args, _ctx) { obj.public_send(edges_prop) }
27
+ nodes_resolver = ->(obj, _args, _ctx) { obj.public_send(nodes_prop) }
28
+ end
29
+
30
+ _original_resolve = field.resolve_proc
31
+ new_resolve_proc = ->(obj, args, ctx) do
32
+ ConnectionEdgesAndNodes.new(obj, args, ctx,
33
+ edges_prop, nodes_prop, edge_to_node_property,
34
+ edges_resolver, nodes_resolver)
35
+ end
36
+
37
+ field.redefine { resolve(new_resolve_proc) }
38
+ end
39
+ # rubocop:enable Metrics/AbcSize
40
+
41
+ private
42
+
43
+ def edge_with_node_connection?(field)
44
+ field.connection? && field.type.fields['edges'].metadata.key?(:_new_includable_connection_marker)
45
+ end
46
+
47
+ def proc_based?(field)
48
+ required_metadata = [:resolve_edges, :resolve_nodes]
49
+ has_a_resolver = required_metadata.any? { |key| field.metadata.key?(key) }
50
+
51
+ return false unless has_a_resolver
52
+ unless required_metadata.all? { |key| field.metadata.key?(key) }
53
+ raise ArgumentError, "Missing one of #{required_metadata}"
54
+ end
55
+
56
+ true
57
+ end
58
+
59
+ def validate!(field, is_proc_based)
60
+ unless field.metadata.key?(:connection_properties)
61
+ raise ArgumentError, 'Missing connection_properties definition for field'
62
+ end
63
+ properties = field.metadata[:connection_properties]
64
+ unless properties.is_a?(Hash)
65
+ raise ArgumentError, 'Connection includes must be a hash containing :edges and :nodes keys'
66
+ end
67
+ raise ArgumentError, 'Missing :nodes' unless is_proc_based || properties.key?(:nodes)
68
+ raise ArgumentError, 'Missing :edges' unless is_proc_based || properties.key?(:edges)
69
+ raise ArgumentError, 'Missing :edge_to_node' unless properties.key?(:edge_to_node)
70
+ end
71
+ end
72
+
73
+ GraphQL::Relay::BaseConnection.register_connection_implementation(
74
+ GraphQLIncludable::New::Relay::ConnectionEdgesAndNodes,
75
+ GraphQLIncludable::New::Relay::EdgeWithNodeConnection
76
+ )
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,177 @@
1
+ GraphQL::Field.accepts_definitions(
2
+ ##
3
+ # Define Active Record includes for a field
4
+ new_includes: GraphQL::Define.assign_metadata_key(:new_includes)
5
+ )
6
+
7
+ module GraphQLIncludable
8
+ module New
9
+ class Resolver
10
+ def initialize(ctx)
11
+ @root_ctx = ctx
12
+ end
13
+
14
+ def includes_for_node(node, includes)
15
+ return includes_for_top_level_connection(node, includes) if node.definition.connection?
16
+
17
+ children = node.scoped_children[node.return_type.unwrap]
18
+ children.each_value do |child_node|
19
+ includes_for_child(child_node, includes)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def includes_for_child(node, includes)
26
+ return includes_for_connection(node, includes) if node.definition.connection?
27
+
28
+ builder = build_includes(node)
29
+ return unless builder&.includes?
30
+
31
+ includes.merge_includes(builder.includes)
32
+
33
+ # Determine which [nested] child Includes manager to send to the children
34
+ child_includes = builder.path_leaf_includes
35
+
36
+ children = node.scoped_children[node.return_type.unwrap]
37
+ children.each_value do |child_node|
38
+ includes_for_child(child_node, child_includes)
39
+ end
40
+ end
41
+
42
+ # rubocop:disable Metrics/AbcSize
43
+ # rubocop:disable Metrics/MethodLength
44
+ def includes_for_connection(node, includes)
45
+ builder = build_connection_includes(node)
46
+ return unless builder&.includes?
47
+
48
+ connection_children = node.scoped_children[node.return_type.unwrap]
49
+ connection_children.each_value do |connection_node|
50
+ # connection_field {
51
+ # totalCount
52
+ # pageInfo {...}
53
+ # nodes {
54
+ # node_model_field ...
55
+ # }
56
+ # edges {
57
+ # edge_model_field ...
58
+ # node {
59
+ # node_model_field ...
60
+ # }
61
+ # }
62
+ # }
63
+
64
+ if connection_node.name == 'edges'
65
+ edges_includes_builder = builder.edges_builder.builder
66
+ includes.merge_includes(edges_includes_builder.includes)
67
+ edges_includes = edges_includes_builder.path_leaf_includes
68
+
69
+ edge_children = connection_node.scoped_children[connection_node.return_type.unwrap]
70
+ edge_children.each_value do |edge_child_node|
71
+ if edge_child_node.name == 'node'
72
+ node_includes_builder = builder.edges_builder.node_builder
73
+ edges_includes.merge_includes(node_includes_builder.includes)
74
+ edge_node_includes = node_includes_builder.path_leaf_includes
75
+
76
+ node_children = edge_child_node.scoped_children[edge_child_node.return_type.unwrap]
77
+ node_children.each_value do |node_child_node|
78
+ includes_for_child(node_child_node, edge_node_includes)
79
+ end
80
+ else
81
+ includes_for_child(edge_child_node, edges_includes)
82
+ end
83
+ end
84
+ elsif connection_node.name == 'nodes'
85
+ nodes_includes_builder = builder.nodes_builder
86
+ includes.merge_includes(nodes_includes_builder.includes)
87
+ nodes_includes = nodes_includes_builder.path_leaf_includes
88
+
89
+ node_children = connection_node.scoped_children[connection_node.return_type.unwrap]
90
+ node_children.each_value do |node_child_node|
91
+ includes_for_child(node_child_node, nodes_includes)
92
+ end
93
+ elsif connection_node.name == 'totalCount'
94
+ # Handled using `.size`
95
+ end
96
+ end
97
+ end
98
+
99
+ # Special case:
100
+ # When includes_for_node is called within a connection resolver, there is no need to use that field's nodes/edges
101
+ # includes, only edge_to_node includes
102
+ def includes_for_top_level_connection(node, includes)
103
+ connection_children = node.scoped_children[node.return_type.unwrap]
104
+ top_level_being_resolved = @root_ctx.namespace(:gql_includable)[:resolving]
105
+
106
+ if top_level_being_resolved == :edges
107
+ builder = build_connection_includes(node)
108
+ return unless builder&.edges_builder&.node_builder&.includes?
109
+
110
+ edges_node = connection_children['edges']
111
+ edges_includes = includes
112
+
113
+ edge_children = edges_node.scoped_children[edges_node.return_type.unwrap]
114
+ edge_children.each_value do |edge_child_node|
115
+ if edge_child_node.name == 'node'
116
+ node_includes_builder = builder.edges_builder.node_builder
117
+ edges_includes.merge_includes(node_includes_builder.includes)
118
+ edge_node_includes = node_includes_builder.path_leaf_includes
119
+
120
+ node_children = edge_child_node.scoped_children[edge_child_node.return_type.unwrap]
121
+ node_children.each_value do |node_child_node|
122
+ includes_for_child(node_child_node, edge_node_includes)
123
+ end
124
+ else
125
+ includes_for_child(edge_child_node, edges_includes)
126
+ end
127
+ end
128
+ else
129
+ nodes_node = connection_children['nodes']
130
+ return unless nodes_node.present?
131
+ nodes_includes = includes
132
+
133
+ node_children = nodes_node.scoped_children[nodes_node.return_type.unwrap]
134
+ node_children.each_value do |node_child_node|
135
+ includes_for_child(node_child_node, nodes_includes)
136
+ end
137
+ end
138
+ end
139
+ # rubocop:enable Metrics/MethodLength
140
+ # rubocop:enable Metrics/AbcSize
141
+
142
+ def build_includes(node)
143
+ includes_meta = node.definition.metadata[:new_includes]
144
+ return nil if includes_meta.blank?
145
+
146
+ builder = GraphQLIncludable::New::IncludesBuilder.new
147
+
148
+ if includes_meta.is_a?(Proc)
149
+ if includes_meta.arity == 2
150
+ args_for_field = @root_ctx.query.arguments_for(node, node.definition)
151
+ builder.instance_exec(args_for_field, @root_ctx, &includes_meta)
152
+ else
153
+ builder.instance_exec(&includes_meta)
154
+ end
155
+ else
156
+ builder.path(includes_meta)
157
+ end
158
+
159
+ builder
160
+ end
161
+
162
+ def build_connection_includes(node)
163
+ includes_meta = node.definition.metadata[:new_includes]
164
+ return nil if includes_meta.blank?
165
+
166
+ builder = GraphQLIncludable::New::ConnectionIncludesBuilder.new
167
+ if includes_meta.arity == 2
168
+ args_for_field = @root_ctx.query.arguments_for(node, node.definition)
169
+ builder.instance_exec(args_for_field, @root_ctx, &includes_meta)
170
+ else
171
+ builder.instance_exec(&includes_meta)
172
+ end
173
+ builder
174
+ end
175
+ end
176
+ end
177
+ end
@@ -47,7 +47,7 @@ module GraphQLIncludable
47
47
  end
48
48
 
49
49
  def page_info
50
- @nodes = determin_page_info_nodes
50
+ @nodes = determine_page_info_nodes
51
51
  super
52
52
  end
53
53
 
@@ -56,7 +56,7 @@ module GraphQLIncludable
56
56
  end
57
57
 
58
58
  def total_count
59
- @nodes = determin_page_info_nodes
59
+ @nodes = determine_page_info_nodes
60
60
  @nodes.size
61
61
  end
62
62
 
@@ -70,7 +70,7 @@ module GraphQLIncludable
70
70
  @edges_and_nodes.ctx
71
71
  end
72
72
 
73
- def determin_page_info_nodes
73
+ def determine_page_info_nodes
74
74
  # If the query asks for `pageInfo` before `edges` or `nodes`, we dont directly know which to use most
75
75
  # efficently. We can have a guess by checking if either of the associations are preloaded
76
76
  return @loaded_nodes if @loaded_nodes.present?
@@ -6,6 +6,7 @@ require 'graphql_includable/relay/edge_with_node'
6
6
  require 'graphql_includable/relay/edge_with_node_connection'
7
7
  require 'graphql_includable/relay/edge_with_node_connection_type'
8
8
  require 'graphql_includable/relay/instrumentation/connection'
9
+ require 'graphql_includable/new/gql_includable'
9
10
 
10
11
  GraphQL::Field.accepts_definitions(
11
12
  includes: GraphQL::Define.assign_metadata_key(:includes),
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.0
4
+ version: 0.5.0.beta.1
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-08-07 00:00:00.000000000 Z
13
+ date: 2019-08-28 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description:
16
16
  email:
@@ -24,6 +24,14 @@ files:
24
24
  - lib/graphql_includable.rb
25
25
  - lib/graphql_includable/concern.rb
26
26
  - lib/graphql_includable/edge.rb
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
27
35
  - lib/graphql_includable/relay/edge_with_node.rb
28
36
  - lib/graphql_includable/relay/edge_with_node_connection.rb
29
37
  - lib/graphql_includable/relay/edge_with_node_connection_type.rb
@@ -44,9 +52,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
52
  version: '0'
45
53
  required_rubygems_version: !ruby/object:Gem::Requirement
46
54
  requirements:
47
- - - ">="
55
+ - - ">"
48
56
  - !ruby/object:Gem::Version
49
- version: '0'
57
+ version: 1.3.1
50
58
  requirements: []
51
59
  rubyforge_project:
52
60
  rubygems_version: 2.6.14.1