# frozen_string_literal: true module GraphQL module Types module Relay # Use this to implement Relay connections, or take it as inspiration # for Relay classes in your own app. # # You may wish to copy this code into your own base class, # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`. # # @example Implementation a connection and edge # # Given some object in your app ... # class Types::Post < BaseObject # end # # # Make a couple of base classes: # class Types::BaseEdge < GraphQL::Types::Relay::BaseEdge; end # class Types::BaseConnection < GraphQL::Types::Relay::BaseConnection; end # # # Then extend them for the object in your app # class Types::PostEdge < Types::BaseEdge # node_type(Types::Post) # end # class Types::PostConnection < Types::BaseConnection # edge_type(Types::PostEdge) # end # # @see Relay::BaseEdge for edge types class BaseConnection < Types::Relay::BaseObject extend Forwardable def_delegators :@object, :cursor_from_node, :parent class << self # @return [Class] attr_reader :node_type # @return [Class] attr_reader :edge_class # Configure this connection to return `edges` and `nodes` based on `edge_type_class`. # # This method will use the inputs to create: # - `edges` field # - `nodes` field # - description # # It's called when you subclass this base connection, trying to use the # class name to set defaults. You can call it again in the class definition # to override the default (or provide a value, if the default lookup failed). def edge_type(edge_type_class, edge_class: GraphQL::Relay::Edge, node_type: edge_type_class.node_type, nodes_field: true, node_nullable: true) # Set this connection's graphql name node_type_name = node_type.graphql_name @node_type = node_type @edge_type = edge_type_class @edge_class = edge_class field :edges, [edge_type_class, null: true], null: true, description: "A list of edges.", edge_class: edge_class define_nodes_field(node_nullable) if nodes_field description("The connection type for #{node_type_name}.") end # Filter this list according to the way its node type would scope them def scope_items(items, context) node_type.scope_items(items, context) end # Add the shortcut `nodes` field to this connection and its subclasses def nodes_field define_nodes_field end def authorized?(obj, ctx) true # Let nodes be filtered out end def accessible?(ctx) node_type.accessible?(ctx) end def visible?(ctx) node_type.visible?(ctx) end private def define_nodes_field(nullable = true) type = nullable ? [@node_type, null: true] : [@node_type] field :nodes, type, null: nullable, description: "A list of nodes.", connection: false end end field :page_info, GraphQL::Types::Relay::PageInfo, null: false, description: "Information to aid in pagination." # By default this calls through to the ConnectionWrapper's edge nodes method, # but sometimes you need to override it to support the `nodes` field def nodes @object.edge_nodes end def edges if @object.is_a?(GraphQL::Pagination::Connection) @object.edges elsif context.interpreter? context.schema.after_lazy(object.edge_nodes) do |nodes| nodes.map { |n| self.class.edge_class.new(n, object) } end else # This is done by edges_instrumentation @object.edge_nodes end end end end end end