# frozen_string_literal: truemoduleGraphQLmoduleRelay# Subclasses must implement:# - {#cursor_from_node}, which returns an opaque cursor for the given item# - {#sliced_nodes}, which slices by `before` & `after`# - {#paged_nodes}, which applies `first` & `last` limits## In a subclass, you have access to# - {#nodes}, the collection which the connection will wrap# - {#first}, {#after}, {#last}, {#before} (arguments passed to the field)# - {#max_page_size} (the specified maximum page size that can be returned from a connection)#classBaseConnection# Just to encode data in the cursor, use something that won't conflictCURSOR_SEPARATOR="---"# Map of collection class names -> connection_classes# eg `{"Array" => ArrayConnection}`CONNECTION_IMPLEMENTATIONS={}class<<self# Find a connection implementation suitable for exposing `nodes`## @param nodes [Object] A collection of nodes (eg, Array, AR::Relation)# @return [subclass of BaseConnection] a connection Class for wrapping `nodes`defconnection_for_nodes(nodes)# If it's a new-style connection object, it's already ready to goifnodes.is_a?(GraphQL::Pagination::Connection)returnnodesend# Check for class _names_ because classes can be redefined in Rails developmentnodes.class.ancestors.eachdo|ancestor|conn_impl=CONNECTION_IMPLEMENTATIONS[ancestor.name]ifconn_implreturnconn_implendend# Should have found a connection during the loop:raise("No connection implementation to wrap #{nodes.class} (#{nodes})")end# Add `connection_class` as the connection wrapper for `nodes_class`# eg, `RelationConnection` is the implementation for `AR::Relation`# @param nodes_class [Class] A class representing a collection (eg, Array, AR::Relation)# @param connection_class [Class] A class implementing Connection methodsdefregister_connection_implementation(nodes_class,connection_class)CONNECTION_IMPLEMENTATIONS[nodes_class.name]=connection_classendendattr_reader:nodes,:arguments,:max_page_size,:parent,:field,:context# Make a connection, wrapping `nodes`# @param nodes [Object] The collection of nodes# @param arguments [GraphQL::Query::Arguments] Query arguments# @param field [GraphQL::Field] The underlying field# @param max_page_size [Int] The maximum number of results to return# @param parent [Object] The object which this collection belongs to# @param context [GraphQL::Query::Context] The context from the field being resolveddefinitialize(nodes,arguments,field: nil,max_page_size: nil,parent: nil,context: nil)@context=context@nodes=nodes@arguments=arguments@field=field@parent=parent@encoder=context?@context.schema.cursor_encoder:GraphQL::Schema::Base64Encoder@max_page_size=max_page_size.nil?&&context?@context.schema.default_max_page_size:max_page_sizeenddefencode(data)@encoder.encode(data,nonce: true)enddefdecode(data)@encoder.decode(data,nonce: true)end# The value passed as `first:`, if there was one. Negative numbers become `0`.# @return [Integer, nil]deffirst@first||=begincapped=limit_pagination_argument(arguments[:first],max_page_size)ifcapped.nil?&&last.nil?capped=max_page_sizeendcappedendend# The value passed as `after:`, if there was one# @return [String, nil]defafterarguments[:after]end# The value passed as `last:`, if there was one. Negative numbers become `0`.# @return [Integer, nil]deflast@last||=limit_pagination_argument(arguments[:last],max_page_size)end# The value passed as `before:`, if there was one# @return [String, nil]defbeforearguments[:before]end# These are the nodes to render for this connection,# probably wrapped by {GraphQL::Relay::Edge}defedge_nodes@edge_nodes||=paged_nodesend# Support the `pageInfo` fielddefpage_infoselfend# Used by `pageInfo`defhas_next_page!!(first&&sliced_nodes.count>first)end# Used by `pageInfo`defhas_previous_page!!(last&&sliced_nodes.count>last)end# Used by `pageInfo`defstart_cursorifstart_node=(respond_to?(:paged_nodes_array,true)?paged_nodes_array:paged_nodes).firstreturncursor_from_node(start_node)elsereturnnilendend# Used by `pageInfo`defend_cursorifend_node=(respond_to?(:paged_nodes_array,true)?paged_nodes_array:paged_nodes).lastreturncursor_from_node(end_node)elsereturnnilendend# An opaque operation which returns a connection-specific cursor.defcursor_from_node(object)raiseGraphQL::RequiredImplementationMissingError,"must return a cursor for this object/connection pair"enddefinspect"#<GraphQL::Relay::Connection @parent=#{@parent.inspect} @arguments=#{@arguments.to_h.inspect}>"endprivate# @param argument [nil, Integer] `first` or `last`, as provided by the client# @param max_page_size [nil, Integer]# @return [nil, Integer] `nil` if the input was `nil`, otherwise a value between `0` and `max_page_size`deflimit_pagination_argument(argument,max_page_size)ifargumentifargument<0argument=0elsifmax_page_size&&argument>max_page_sizeargument=max_page_sizeendendargumentenddefpaged_nodesraiseGraphQL::RequiredImplementationMissingError,"must return nodes for this connection after paging"enddefsliced_nodesraiseGraphQL::RequiredImplementationMissingError,"must return all nodes for this connection after chopping off first and last"endendendend