class GraphQLIncludable::Resolver

Public Class Methods

new(ctx) click to toggle source
# File lib/graphql_includable/resolver.rb, line 9
def initialize(ctx)
  @root_ctx = ctx
end

Public Instance Methods

includes_for_node(node, includes) click to toggle source
# File lib/graphql_includable/resolver.rb, line 13
def includes_for_node(node, includes)
  return includes_for_top_level_connection(node, includes) if node.definition.connection?

  children = node.scoped_children[node.return_type.unwrap]
  children.each_value do |child_node|
    definition_override = node_definition_override(node, child_node)
    includes_for_child(child_node, includes, definition_override)
  end
end

Private Instance Methods

build_connection_includes(node, definition_override) click to toggle source
# File lib/graphql_includable/resolver.rb, line 171
def build_connection_includes(node, definition_override)
  definition = definition_override || node.definition
  includes_meta = definition.metadata[:includes]
  return nil if includes_meta.blank?

  builder = GraphQLIncludable::ConnectionIncludesBuilder.new
  if includes_meta.arity == 2
    args_for_field = @root_ctx.query.arguments_for(node, node.definition)
    builder.instance_exec(args_for_field, @root_ctx, &includes_meta)
  else
    builder.instance_exec(&includes_meta)
  end
  builder
end
build_includes(node, definition_override) click to toggle source

rubocop:enable Metrics/MethodLength rubocop:enable Metrics/AbcSize

# File lib/graphql_includable/resolver.rb, line 150
def build_includes(node, definition_override)
  definition = definition_override || node.definition
  includes_meta = definition.metadata[:includes]
  return nil if includes_meta.blank?

  builder = GraphQLIncludable::IncludesBuilder.new

  if includes_meta.is_a?(Proc)
    if includes_meta.arity == 2
      args_for_field = @root_ctx.query.arguments_for(node, node.definition)
      builder.instance_exec(args_for_field, @root_ctx, &includes_meta)
    else
      builder.instance_exec(&includes_meta)
    end
  else
    builder.path(includes_meta)
  end

  builder
end
includes_for_child(node, includes, definition_override) click to toggle source
# File lib/graphql_includable/resolver.rb, line 25
def includes_for_child(node, includes, definition_override)
  return includes_for_connection(node, includes, definition_override) if node.definition.connection?

  builder = build_includes(node, definition_override)
  return unless builder.present?
  includes.merge_includes(builder.includes) unless builder.includes.empty?

  return unless builder.includes?

  # Determine which [nested] child Includes manager to send to the children
  child_includes = includes.dig(builder.included_path)

  children = node.scoped_children[node.return_type.unwrap]
  children.each_value do |child_node|
    definition_override = node_definition_override(node, child_node)
    includes_for_child(child_node, child_includes, definition_override)
  end
end
includes_for_connection(node, includes, definition_override) click to toggle source

rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength

# File lib/graphql_includable/resolver.rb, line 46
def includes_for_connection(node, includes, definition_override)
  builder = build_connection_includes(node, definition_override)
  return unless builder&.includes?

  connection_children = node.scoped_children[node.return_type.unwrap]
  connection_children.each_value do |connection_node|
    # connection_field {
    #   totalCount
    #   pageInfo {...}
    #   nodes {
    #     node_model_field ...
    #   }
    #   edges {
    #     edge_model_field ...
    #     node {
    #       node_model_field ...
    #     }
    #   }
    # }

    if connection_node.name == 'edges'
      edges_includes_builder = builder.edges_builder.builder
      includes.merge_includes(edges_includes_builder.includes)
      edges_includes = edges_includes_builder.path_leaf_includes

      edge_children = connection_node.scoped_children[connection_node.return_type.unwrap]
      edge_children.each_value do |edge_child_node|
        if edge_child_node.name == 'node'
          node_includes_builder = builder.edges_builder.node_builder
          edges_includes.merge_includes(node_includes_builder.includes)
          edge_node_includes = node_includes_builder.path_leaf_includes

          node_children = edge_child_node.scoped_children[edge_child_node.return_type.unwrap]
          node_children.each_value do |node_child_node|
            definition_override = node_definition_override(edge_child_node, node_child_node)
            includes_for_child(node_child_node, edge_node_includes, definition_override)
          end
        else
          definition_override = node_definition_override(connection_node, edge_child_node)
          includes_for_child(edge_child_node, edges_includes, definition_override)
        end
      end
    elsif connection_node.name == 'nodes'
      nodes_includes_builder = builder.nodes_builder
      includes.merge_includes(nodes_includes_builder.includes)
      nodes_includes = nodes_includes_builder.path_leaf_includes

      node_children = connection_node.scoped_children[connection_node.return_type.unwrap]
      node_children.each_value do |node_child_node|
        definition_override = node_definition_override(connection_node, node_child_node)
        includes_for_child(node_child_node, nodes_includes, definition_override)
      end
    elsif connection_node.name == 'totalCount'
      # Handled using `.size`
    end
  end
end
includes_for_top_level_connection(node, includes) click to toggle source

Special case: When includes_for_node is called within a connection resolver, there is no need to use that field's nodes/edges includes, only edge_to_node includes

# File lib/graphql_includable/resolver.rb, line 107
def includes_for_top_level_connection(node, includes)
  connection_children = node.scoped_children[node.return_type.unwrap]
  top_level_being_resolved = @root_ctx.namespace(:gql_includable)[:resolving]

  if top_level_being_resolved == :edges
    builder = build_connection_includes(node, nil)
    return unless builder&.edges_builder&.node_builder&.includes?

    edges_node = connection_children['edges']
    edges_includes = includes

    edge_children = edges_node.scoped_children[edges_node.return_type.unwrap]
    edge_children.each_value do |edge_child_node|
      if edge_child_node.name == 'node'
        node_includes_builder = builder.edges_builder.node_builder
        edges_includes.merge_includes(node_includes_builder.includes)
        edge_node_includes = node_includes_builder.path_leaf_includes

        node_children = edge_child_node.scoped_children[edge_child_node.return_type.unwrap]
        node_children.each_value do |node_child_node|
          definition_override = node_definition_override(edge_child_node, node_child_node)
          includes_for_child(node_child_node, edge_node_includes, definition_override)
        end
      else
        definition_override = node_definition_override(edges_node, edge_child_node)
        includes_for_child(edge_child_node, edges_includes, definition_override)
      end
    end
  else
    nodes_node = connection_children['nodes']
    return unless nodes_node.present?
    nodes_includes = includes

    node_children = nodes_node.scoped_children[nodes_node.return_type.unwrap]
    node_children.each_value do |node_child_node|
      definition_override = node_definition_override(nodes_node, node_child_node)
      includes_for_child(node_child_node, nodes_includes, definition_override)
    end
  end
end
node_definition_override(parent_node, child_node) click to toggle source
# File lib/graphql_includable/resolver.rb, line 186
def node_definition_override(parent_node, child_node)
  node_return_type = parent_node.return_type.unwrap
  child_node_parent_type = child_node.parent.return_type.unwrap

  return nil unless child_node_parent_type != node_return_type
  child_node_definition_override = nil
  # Handle GraphQL interface with overridden fields
  # GraphQL makes child_node.return_type the interface instance
  # and therefore takes the metadata from the interface rather than the
  # implementing object's overridden field instance
  is_interface = node_return_type.interfaces.include?(child_node_parent_type)
  child_node_definition_override = node_return_type.fields[child_node.name] if is_interface
  child_node_definition_override
end