class GraphQL::Pagination::Connection

A Connection wraps a list of items and provides cursor-based pagination over it.

Connections were introduced by Facebook's `Relay` front-end framework, but proved to be generally useful for GraphQL APIs. When in doubt, use connections to serve lists (like Arrays, ActiveRecord::Relations) via GraphQL.

Unlike the previous connection implementation, these default to bidirectional pagination.

Pagination arguments and context may be provided at initialization or assigned later (see {Schema::Field::ConnectionExtension}).

Attributes

after_value[RW]

Raw access to client-provided values. (`max_page_size` not applied to first or last.)

arguments[RW]

@return [Hash<Symbol => Object>] The field arguments from the field that returned this connection

before_value[RW]

Raw access to client-provided values. (`max_page_size` not applied to first or last.)

context[RW]

@return [GraphQL::Query::Context]

edge_class[RW]

@return [Class] A wrapper class for edges of this connection

field[RW]

@return [GraphQL::Schema::Field] The field this connection was returned by

first[W]
first_value[RW]

Raw access to client-provided values. (`max_page_size` not applied to first or last.)

items[R]

@return [Object] A list object, from the application. This is the unpaginated value passed into the connection.

last[W]
last_value[RW]

Raw access to client-provided values. (`max_page_size` not applied to first or last.)

parent[RW]

@return [Object] the object this collection belongs to

Public Class Methods

new(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil, edge_class: nil, arguments: nil) click to toggle source

@param items [Object] some unpaginated collection item, like an `Array` or `ActiveRecord::Relation` @param context [Query::Context] @param parent [Object] The object this collection belongs to @param first [Integer, nil] The limit parameter from the client, if it provided one @param after [String, nil] A cursor for pagination, if the client provided one @param last [Integer, nil] Limit parameter from the client, if provided @param before [String, nil] A cursor for pagination, if the client provided one. @param arguments [Hash] The arguments to the field that returned the collection wrapped by this connection @param max_page_size [Integer, nil] A configured value to cap the result size. Applied as `first` if neither first or last are given.

# File lib/graphql/pagination/connection.rb, line 60
def initialize(items, parent: nil, field: nil, context: nil, first: nil, after: nil, max_page_size: :not_given, last: nil, before: nil, edge_class: nil, arguments: nil)
  @items = items
  @parent = parent
  @context = context
  @field = field
  @first_value = first
  @after_value = after
  @last_value = last
  @before_value = before
  @arguments = arguments
  @edge_class = edge_class || self.class::Edge
  # This is only true if the object was _initialized_ with an override
  # or if one is assigned later.
  @has_max_page_size_override = max_page_size != :not_given
  @max_page_size = if max_page_size == :not_given
    nil
  else
    max_page_size
  end
end

Public Instance Methods

after() click to toggle source

@return [String, nil] the client-provided cursor. `“”` is treated as `nil`.

# File lib/graphql/pagination/connection.rb, line 40
def after
  if defined?(@after)
    @after
  else
    @after = @after_value == "" ? nil : @after_value
  end
end
before() click to toggle source

@return [String, nil] the client-provided cursor. `“”` is treated as `nil`.

# File lib/graphql/pagination/connection.rb, line 31
def before
  if defined?(@before)
    @before
  else
    @before = @before_value == "" ? nil : @before_value
  end
end
cursor_for(item) click to toggle source

Return a cursor for this item. @param item [Object] one of the passed in {items}, taken from {nodes} @return [String]

# File lib/graphql/pagination/connection.rb, line 178
def cursor_for(item)
  raise PaginationImplementationMissingError, "Implement #{self.class}#cursor_for(item) to return the cursor for #{item.inspect}"
end
edge_nodes() click to toggle source

A dynamic alias for compatibility with {Relay::BaseConnection}. @deprecated use {#nodes} instead

# File lib/graphql/pagination/connection.rb, line 146
def edge_nodes
  nodes
end
edges() click to toggle source

@return [Array<Edge>] {nodes}, but wrapped with Edge instances

# File lib/graphql/pagination/connection.rb, line 129
def edges
  @edges ||= nodes.map { |n| @edge_class.new(n, self) }
end
end_cursor() click to toggle source

@return [String] The cursor of the last item in {nodes}

# File lib/graphql/pagination/connection.rb, line 171
def end_cursor
  nodes.last && cursor_for(nodes.last)
end
first() click to toggle source

@return [Integer, nil]

A clamped `first` value.
(The underlying instance variable doesn't have limits on it.)
If neither `first` nor `last` is given, but `max_page_size` is present, max_page_size is used for first.
# File lib/graphql/pagination/connection.rb, line 103
def first
  @first ||= begin
    capped = limit_pagination_argument(@first_value, max_page_size)
    if capped.nil? && last.nil?
      capped = max_page_size
    end
    capped
  end
end
has_max_page_size_override?() click to toggle source
# File lib/graphql/pagination/connection.rb, line 94
def has_max_page_size_override?
  @has_max_page_size_override
end
has_next_page() click to toggle source

@return [Boolean] True if there are more items after this page

# File lib/graphql/pagination/connection.rb, line 156
def has_next_page
  raise PaginationImplementationMissingError, "Implement #{self.class}#has_next_page to return the next-page check"
end
has_previous_page() click to toggle source

@return [Boolean] True if there were items before these items

# File lib/graphql/pagination/connection.rb, line 161
def has_previous_page
  raise PaginationImplementationMissingError, "Implement #{self.class}#has_previous_page to return the previous-page check"
end
last() click to toggle source

@return [Integer, nil] A clamped `last` value. (The underlying instance variable doesn't have limits on it)

# File lib/graphql/pagination/connection.rb, line 124
def last
  @last ||= limit_pagination_argument(@last_value, max_page_size)
end
max_page_size() click to toggle source
# File lib/graphql/pagination/connection.rb, line 86
def max_page_size
  if @has_max_page_size_override
    @max_page_size
  else
    context.schema.default_max_page_size
  end
end
max_page_size=(new_value) click to toggle source
# File lib/graphql/pagination/connection.rb, line 81
def max_page_size=(new_value)
  @has_max_page_size_override = true
  @max_page_size = new_value
end
nodes() click to toggle source

@return [Array<Object>] A slice of {items}, constrained by {@first_value}/{@after_value}/{@last_value}/{@before_value}

# File lib/graphql/pagination/connection.rb, line 140
def nodes
  raise PaginationImplementationMissingError, "Implement #{self.class}#nodes to paginate `@items`"
end
page_info() click to toggle source

The connection object itself implements `PageInfo` fields

# File lib/graphql/pagination/connection.rb, line 151
def page_info
  self
end
range_add_edge(item) click to toggle source

This is called by `Relay::RangeAdd` – it can be overridden when `item` needs some modifications based on this connection's state.

@param item [Object] An item newly added to `items` @return [Edge]

# File lib/graphql/pagination/connection.rb, line 118
def range_add_edge(item)
  edge_class.new(item, self)
end
start_cursor() click to toggle source

@return [String] The cursor of the first item in {nodes}

# File lib/graphql/pagination/connection.rb, line 166
def start_cursor
  nodes.first && cursor_for(nodes.first)
end

Private Instance Methods

decode(cursor) click to toggle source
# File lib/graphql/pagination/connection.rb, line 198
def decode(cursor)
  context.schema.cursor_encoder.decode(cursor, nonce: true)
end
encode(cursor) click to toggle source
# File lib/graphql/pagination/connection.rb, line 202
def encode(cursor)
  context.schema.cursor_encoder.encode(cursor, nonce: true)
end
limit_pagination_argument(argument, max_page_size) click to toggle source

@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`

# File lib/graphql/pagination/connection.rb, line 187
def limit_pagination_argument(argument, max_page_size)
  if argument
    if argument < 0
      argument = 0
    elsif max_page_size && argument > max_page_size
      argument = max_page_size
    end
  end
  argument
end