class GraphQL::Pagination::RelationConnection

A generic class for working with database query objects.

def after_offset

Returns:
  • (Integer, nil) -
def after_offset
  @after_offset ||= after && offset_from_cursor(after)
end

def before_offset

Returns:
  • (Integer, nil) -
def before_offset
  @before_offset ||= before && offset_from_cursor(before)
end

def cursor_for(item)

def cursor_for(item)
  load_nodes
  # index in nodes + existing offset + 1 (because it's offset, not index)
  offset = nodes.index(item) + 1 + (@paged_nodes_offset || 0) + (relation_offset(items) || 0)
  encode(offset.to_s)
end

def has_next_page

def has_next_page
  if @has_next_page.nil?
    @has_next_page = if before_offset && before_offset > 0
      true
    elsif first
      if @nodes && @nodes.count < first
        false
      else
        relation_larger_than(sliced_nodes, first)
      end
    else
      false
    end
  end
  @has_next_page
end

def has_previous_page

def has_previous_page
  if @has_previous_page.nil?
    @has_previous_page = if after_offset && after_offset > 0
      true
    elsif last
      # See whether there are any nodes _before_ the current offset.
      # If there _is no_ current offset, then there can't be any nodes before it.
      # Assume that if the offset is positive, there are nodes before the offset.
      limited_nodes
      !(@paged_nodes_offset.nil? || @paged_nodes_offset == 0)
    else
      false
    end
  end
  @has_previous_page
end

def limited_nodes

returning a new relation
Apply `first` and `last` to `sliced_nodes`,
def limited_nodes
  @limited_nodes ||= begin
    paginated_nodes = sliced_nodes
    previous_limit = relation_limit(paginated_nodes)
    if first && (previous_limit.nil? || previous_limit > first)
      # `first` would create a stricter limit that the one already applied, so add it
      paginated_nodes = set_limit(paginated_nodes, first)
    end
    if last
      if (lv = relation_limit(paginated_nodes))
        if last <= lv
          # `last` is a smaller slice than the current limit, so apply it
          offset = (relation_offset(paginated_nodes) || 0) + (lv - last)
          paginated_nodes = set_offset(paginated_nodes, offset)
          paginated_nodes = set_limit(paginated_nodes, last)
        end
      else
        # No limit, so get the last items
        sliced_nodes_count = relation_count(@sliced_nodes)
        offset = (relation_offset(paginated_nodes) || 0) + sliced_nodes_count - [last, sliced_nodes_count].min
        paginated_nodes = set_offset(paginated_nodes, offset)
        paginated_nodes = set_limit(paginated_nodes, last)
      end
    end
    @paged_nodes_offset = relation_offset(paginated_nodes)
    paginated_nodes
  end
end

def load_nodes

returns an array of nodes
Load nodes after applying first/last/before/after,
def load_nodes
  # Return an array so we can consistently use `.index(node)` on it
  @nodes ||= limited_nodes.to_a
end

def nodes

def nodes
  load_nodes
  @nodes
end

def null_relation(relation)

Returns:
  • (Object) - A modified query object which will return no records

Parameters:
  • relation (Object) -- A database query object
def null_relation(relation)
  raise "#{self.class}#null_relation(relation) must return an empty relation for a #{relation.class} (#{relation.inspect})"
end

def offset_from_cursor(cursor)

Returns:
  • (Integer) -
def offset_from_cursor(cursor)
  decode(cursor).to_i
end

def relation_count(relation)

Returns:
  • (Integer, nil) - The number of items in this relation (hopefully determined without loading all records into memory!)

Parameters:
  • relation (Object) -- A database query object
def relation_count(relation)
  raise "#{self.class}#relation_count(relation) must return the count of records for a #{relation.class} (#{relation.inspect})"
end

def relation_larger_than(relation, size)

Returns:
  • (Boolean) - True if the number of items in this relation is larger than `size`

Parameters:
  • size (Integer) -- The value against which we check the relation size
  • relation (Object) -- A database query object
def relation_larger_than(relation, size)
  relation_count(set_limit(relation, size + 1)) == size + 1
end

def relation_limit(relation)

Returns:
  • (Integer, nil) - The limit value, or nil if there isn't one

Parameters:
  • relation (Object) -- A database query object
def relation_limit(relation)
  raise "#{self.class}#relation_limit(relation) must return the limit value for a #{relation.class} (#{relation.inspect})"
end

def relation_offset(relation)

Returns:
  • (Integer, nil) - The offset value, or nil if there isn't one

Parameters:
  • relation (Object) -- A database query object
def relation_offset(relation)
  raise "#{self.class}#relation_offset(relation) must return the offset value for a #{relation.class} (#{relation.inspect})"
end

def set_limit(relation, limit_value)

(Sequel doesn't like it, understandably.)
Abstract this operation so we can always ignore inputs less than zero.
def set_limit(relation, limit_value)
  if limit_value > 0
    relation.limit(limit_value)
  elsif limit_value == 0
    null_relation(relation)
  else
    relation
  end
end

def set_offset(relation, offset_value)

(Sequel doesn't like it, understandably.)
Abstract this operation so we can always ignore inputs less than zero.
def set_offset(relation, offset_value)
  if offset_value >= 0
    relation.offset(offset_value)
  else
    relation.offset(0)
  end
end

def sliced_nodes

returning a new relation.
Apply `before` and `after` to the underlying `items`,
def sliced_nodes
  @sliced_nodes ||= begin
    paginated_nodes = items
    if after_offset
      previous_offset = relation_offset(items) || 0
      paginated_nodes = set_offset(paginated_nodes, previous_offset + after_offset)
    end
    if before_offset && after_offset
      if after_offset < before_offset
        # Get the number of items between the two cursors
        space_between = before_offset - after_offset - 1
        paginated_nodes = set_limit(paginated_nodes, space_between)
      else
        # TODO I think this is untested
        # The cursors overextend one another to an empty set
        paginated_nodes = null_relation(paginated_nodes)
      end
    elsif before_offset
      # Use limit to cut off the tail of the relation
      paginated_nodes = set_limit(paginated_nodes, before_offset - 1)
    end
    paginated_nodes
  end
end