graphql_includable 0.3.1 → 0.4.0.alpha.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: 9fff2fd1ed83291d757e0d30bd9d5587067764e6
4
- data.tar.gz: c09ed087d6aafc20002a6302e1aa4d96dba6744d
3
+ metadata.gz: 0b4931965d11fc59002d2f514361dc3d30e0a17b
4
+ data.tar.gz: 2d0d4f23a686bd46d19d3447d989402364f16849
5
5
  SHA512:
6
- metadata.gz: b8bf0ba1e64f04cddba4379c47da8bd60bef9fa35f099b4ccf364ffa3bd4300af13b341acb486a34d945524475995d8e824ddb4874abe83d7953a89ddaa2e6dc
7
- data.tar.gz: 98cfc21bab27745e1f9b5b3496f0906b797df878079c1ef936650296768fd1330e50b8fa63758644aabc03e3fac06f725d74405b28f5d8939f6c0ded6c2dce43
6
+ metadata.gz: 2be6bc900f6e21eadc3eafda2cf236a35b9d1366e67ace38d392ff4499b3a7bfb50576541edc994e5aa22d0a05018a1d9ba3dae2eef56e28323d8139f27e2ca9
7
+ data.tar.gz: c42a7b0b009ed5078e786bdbac8e2c25bcd6f192333bc40c02e3d74f96fa373809ddf3aa419be5e28f080a1b9e325574653284921d107a29b578cb56aa3dab23
@@ -6,17 +6,11 @@ module GraphQLIncludable
6
6
 
7
7
  module ClassMethods
8
8
  def includes_from_graphql(ctx)
9
- ActiveSupport::Notifications.instrument('graphql_includable.includes_from_graphql') do |instrument|
10
- instrument[:operation_name] = ctx.query&.operation_name
11
-
12
- node = Resolver.find_node_by_return_type(ctx.irep_node, name)
13
- manager = IncludesManager.new(nil)
14
- Resolver.includes_for_node(node, manager)
15
-
16
- generated_includes = manager.includes
17
- instrument[:includes] = generated_includes
18
- includes(generated_includes)
19
- end
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
+ puts "INCLUDES!!! #{manager.includes}"
13
+ includes(manager.includes)
20
14
  rescue => e
21
15
  Rails.logger.error(e)
22
16
  self
@@ -3,8 +3,7 @@ module GraphQLIncludable
3
3
  class EdgeWithNode < GraphQL::Relay::Edge
4
4
  def initialize(node, connection)
5
5
  @edge = node
6
- edge_to_node_property = connection.field.type.fields['edges'].metadata[:edge_to_node_property]
7
- node = @edge.public_send(edge_to_node_property)
6
+ node = connection.edge_to_node(@edge) # TODO: Make lazy
8
7
  super(node, connection)
9
8
  end
10
9
 
@@ -1,12 +1,17 @@
1
1
  module GraphQLIncludable
2
2
  module Relay
3
3
  class ConnectionEdgesAndNodes
4
- attr_reader :parent, :edges_property, :nodes_property
4
+ attr_reader :parent, :args, :ctx, :edges_property, :nodes_property, :edge_to_node_property, :edges_resolver, :nodes_resolver
5
5
 
6
- def initialize(parent, edges_property, nodes_property)
6
+ def initialize(parent, args, ctx, edges_property, nodes_property, edge_to_node_property, edges_resolver, nodes_resolver)
7
7
  @parent = parent
8
+ @args = args
9
+ @ctx = ctx
8
10
  @edges_property = edges_property
9
11
  @nodes_property = nodes_property
12
+ @edge_to_node_property = edge_to_node_property
13
+ @edges_resolver = edges_resolver
14
+ @nodes_resolver = nodes_resolver
10
15
  end
11
16
  end
12
17
 
@@ -23,14 +28,14 @@ module GraphQLIncludable
23
28
  end
24
29
 
25
30
  def fetch_edges
26
- @loaded_edges ||= @connection_edges_and_nodes.parent.public_send(@connection_edges_and_nodes.edges_property)
31
+ @loaded_edges ||= @connection_edges_and_nodes.edges_resolver.call(@connection_edges_and_nodes.parent, args, ctx)
27
32
  # Set nodes to make underlying BaseConnection work
28
33
  @nodes = @loaded_edges
29
34
  @loaded_edges
30
35
  end
31
36
 
32
37
  def fetch_nodes
33
- @loaded_nodes ||= @connection_edges_and_nodes.parent.public_send(@connection_edges_and_nodes.nodes_property)
38
+ @loaded_nodes ||= @connection_edges_and_nodes.nodes_resolver.call(@connection_edges_and_nodes.parent, args, ctx)
34
39
  # Set nodes to make underlying BaseConnection work
35
40
  @nodes = @loaded_nodes
36
41
  @loaded_nodes
@@ -41,8 +46,25 @@ module GraphQLIncludable
41
46
  super
42
47
  end
43
48
 
49
+ def edge_to_node(edge)
50
+ edge.public_send(@connection_edges_and_nodes.edge_to_node_property)
51
+ end
52
+
53
+ def total_count
54
+ @nodes = determin_page_info_nodes
55
+ @nodes.size
56
+ end
57
+
44
58
  private
45
59
 
60
+ def args
61
+ @connection_edges_and_nodes.args
62
+ end
63
+
64
+ def ctx
65
+ @connection_edges_and_nodes.ctx
66
+ end
67
+
46
68
  def determin_page_info_nodes
47
69
  # If the query asks for `pageInfo` before `edges` or `nodes`, we dont directly know which to use most efficently.
48
70
  # We can have a guess by checking if either of the associations are preloaded
@@ -2,7 +2,7 @@ module GraphQLIncludable
2
2
  module Relay
3
3
  class EdgeWithNodeConnectionType
4
4
  def self.create_type(
5
- wrapped_type, edge_to_node_property:,
5
+ wrapped_type,
6
6
  edge_type: wrapped_type.edge_type, edge_class: EdgeWithNode, nodes_field: GraphQL::Relay::ConnectionType.default_nodes_field, &block
7
7
  )
8
8
  custom_edge_class = edge_class
@@ -10,7 +10,10 @@ module GraphQLIncludable
10
10
  GraphQL::ObjectType.define do
11
11
  name("#{wrapped_type.name}Connection")
12
12
  description("The connection type for #{wrapped_type.name}.")
13
- field :edges, types[edge_type], "A list of edges.", edge_class: custom_edge_class, property: :fetch_edges, edge_to_node_property: edge_to_node_property
13
+
14
+ field :totalCount, types.Int, "Total count.", property: :total_count
15
+
16
+ field :edges, types[edge_type], "A list of edges.", edge_class: custom_edge_class, property: :fetch_edges, _includable_connection_marker: true
14
17
 
15
18
  if nodes_field
16
19
  field :nodes, types[wrapped_type], "A list of nodes.", property: :fetch_nodes
@@ -3,29 +3,65 @@ module GraphQLIncludable
3
3
  module Instrumentation
4
4
  class Connection
5
5
  def instrument(_type, field)
6
- return field unless field.connection?
6
+ return field unless is_edge_with_node_connection?(field)
7
7
 
8
- required_metadata = [:edges_property, :nodes_property]
9
- requires_instrumentation = required_metadata.any? { |key| field.metadata.key?(key) }
8
+ raise ArgumentError.new('Connection does not support fetching using :property') if field.property.present?
10
9
 
11
- return field unless requires_instrumentation
12
- raise ArgumentError unless required_metadata.all? { |key| field.metadata.key?(key) }
10
+ validate!(field)
11
+ edge_to_node_property = field.metadata[:edge_to_node_property]
12
+ explicit_includes = field.metadata[:includes]
13
+ edges_prop = explicit_includes[:edges]
14
+ nodes_prop = explicit_includes[:nodes]
13
15
 
14
- raise ArgumentError if field.property.present? # TODO: Check for resolve proc too
15
-
16
- edges_prop = field.metadata[:edges_property]
17
- nodes_prop = field.metadata[:nodes_property]
16
+ if is_proc_based?(field)
17
+ edges_resolver = field.metadata[:resolve_edges]
18
+ nodes_resolver = field.metadata[:resolve_nodes]
19
+ else
20
+ # Use the edges and nodes symbols from the incldues pattern as the propeties to fetch
21
+ edges_resolver = ->(obj, args, ctx) { obj.public_send(edges_prop) }
22
+ nodes_resolver = ->(obj, args, ctx) { obj.public_send(nodes_prop) }
23
+ end
18
24
 
19
25
  _original_resolve = field.resolve_proc
20
26
  new_resolve_proc = ->(obj, args, ctx) do
21
- ConnectionEdgesAndNodes.new(obj, edges_prop, nodes_prop)
27
+ ConnectionEdgesAndNodes.new(obj, args, ctx, edges_prop, nodes_prop, edge_to_node_property, edges_resolver, nodes_resolver)
22
28
  end
23
29
 
24
30
  field.redefine { resolve(new_resolve_proc) }
25
31
  end
32
+
33
+ private
34
+
35
+ def is_edge_with_node_connection?(field)
36
+ field.connection? && field.type.fields['edges'].metadata.key?(:_includable_connection_marker)
37
+ end
38
+
39
+ def is_proc_based?(field)
40
+ required_metadata = [:resolve_edges, :resolve_nodes]
41
+ has_a_resolver = required_metadata.any? { |key| field.metadata.key?(key) }
42
+
43
+ return false unless has_a_resolver
44
+ raise ArgumentError.new("Missing one of #{required_metadata}") unless required_metadata.all? { |key| field.metadata.key?(key) }
45
+
46
+ true
47
+ end
48
+
49
+ def validate!(field)
50
+ raise ArgumentError.new('Missing edge_to_node_property definition for field') unless field.metadata.key?(:edge_to_node_property)
51
+ raise ArgumentError.new(':edge_to_node_property must be a symbol') unless field.metadata[:edge_to_node_property].is_a?(Symbol)
52
+
53
+ raise ArgumentError.new('Missing includes definition for field') unless field.metadata.key?(:includes)
54
+ includes = field.metadata[:includes]
55
+ raise ArgumentError.new('Connection includes must be a hash containing :edges and :nodes keys') unless includes.is_a?(Hash)
56
+ raise ArgumentError.new('Missing :nodes includes') unless includes.key?(:nodes)
57
+ raise ArgumentError.new('Missing :edges includes') unless includes.key?(:edges)
58
+ raise ArgumentError.new(':edges must be a symbol') unless includes[:edges].is_a?(Symbol)
59
+ raise ArgumentError.new(':nodes must be a symbol') unless includes[:edges].is_a?(Symbol)
60
+ end
26
61
  end
27
62
 
28
63
  GraphQL::Relay::BaseConnection.register_connection_implementation(GraphQLIncludable::Relay::ConnectionEdgesAndNodes, GraphQLIncludable::Relay::EdgeWithNodeConnection)
29
64
  end
65
+
30
66
  end
31
67
  end
@@ -80,7 +80,7 @@ module GraphQLIncludable
80
80
  edges_association = associations_from_parent_model[:edges]
81
81
  nodes_association = associations_from_parent_model[:nodes]
82
82
 
83
- edge_node_attribute = node.return_type.fields['edges'].metadata[:edge_to_node_property]
83
+ edge_node_attribute = node.definition.metadata[:edge_to_node_property]
84
84
  edge_model = edges_association.klass
85
85
  edge_to_node_association = edge_model.reflect_on_association(edge_node_attribute)
86
86
  node_model = edge_to_node_association.klass
@@ -88,6 +88,7 @@ module GraphQLIncludable
88
88
  connection_children = node.scoped_children[node.return_type.unwrap]
89
89
  connection_children.each_value do |connection_node|
90
90
  # connection_field {
91
+ # totalCount
91
92
  # pageInfo {...}
92
93
  # nodes {
93
94
  # node_model_field ...
@@ -122,6 +123,8 @@ module GraphQLIncludable
122
123
  node_children.each_value do |node_child_node|
123
124
  includes_for_child(node_child_node, node_model, nodes_includes_manager)
124
125
  end
126
+ elsif connection_node.name == 'totalCount'
127
+ # Handled using `.size` - if includes() grabbed edges/nodes it will .length else, a COUNT query saving memory.
125
128
  end
126
129
  end
127
130
  end
@@ -167,15 +170,16 @@ module GraphQLIncludable
167
170
 
168
171
  def self.node_possible_association_names(node)
169
172
  definition = node.definitions.first
173
+ user_includes = definition.metadata[:includes]
170
174
 
171
175
  association_names = {}
172
176
  if node_is_relay_connection?(node)
173
- association_names[:edges] = definition.metadata[:edges_property] if definition.metadata.key?(:edges_property)
174
- association_names[:nodes] = definition.metadata[:nodes_property] if definition.metadata.key?(:nodes_property)
175
- return association_names if association_names.present? # This should be an includable connection with no :property or name fallback.
177
+ association_names[:edges] = user_includes[:edges] if user_includes.key?(:edges)
178
+ association_names[:nodes] = user_includes[:nodes] if user_includes.key?(:nodes)
179
+ return association_names if association_names.present? # This will have resolve procs for nodes and edges
176
180
  end
177
181
 
178
- association_names[:default] = definition.metadata[:includes] || (definition.property || definition.name).to_sym
182
+ association_names[:default] = user_includes || (definition.property || definition.name).to_sym
179
183
  association_names
180
184
  end
181
185
 
@@ -9,9 +9,11 @@ require 'graphql_includable/relay/instrumentation/connection'
9
9
 
10
10
  GraphQL::Field.accepts_definitions(
11
11
  includes: GraphQL::Define.assign_metadata_key(:includes),
12
- edges_property: GraphQL::Define.assign_metadata_key(:edges_property),
13
- nodes_property: GraphQL::Define.assign_metadata_key(:nodes_property),
14
- edge_to_node_property: GraphQL::Define.assign_metadata_key(:edge_to_node_property)
12
+ edge_to_node_property: GraphQL::Define.assign_metadata_key(:edge_to_node_property),
13
+ resolve_edges: GraphQL::Define.assign_metadata_key(:resolve_edges),
14
+ resolve_nodes: GraphQL::Define.assign_metadata_key(:resolve_nodes),
15
+ # Internal use
16
+ _includable_connection_marker: GraphQL::Define.assign_metadata_key(:_includable_connection_marker),
15
17
  )
16
18
 
17
19
  module GraphQL
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.3.1
4
+ version: 0.4.0.alpha.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-15 00:00:00.000000000 Z
13
+ date: 2019-08-06 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description:
16
16
  email:
@@ -44,9 +44,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
44
  version: '0'
45
45
  required_rubygems_version: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - ">="
47
+ - - ">"
48
48
  - !ruby/object:Gem::Version
49
- version: '0'
49
+ version: 1.3.1
50
50
  requirements: []
51
51
  rubyforge_project:
52
52
  rubygems_version: 2.6.14.1