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 calculate_sliced_nodes_parameters

def calculate_sliced_nodes_parameters
  if defined?(@sliced_nodes_limit)
    return
  else
    next_offset = relation_offset(items) || 0
    relation_limit = relation_limit(items)
    if after_offset
      next_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
        relation_limit = space_between
      else
        # The cursors overextend one another to an empty set
        @sliced_nodes_null_relation = true
      end
    elsif before_offset
      # Use limit to cut off the tail of the relation
      relation_limit = before_offset - 1
    end
    @sliced_nodes_limit = relation_limit
    @sliced_nodes_offset = next_offset
  end
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, @sliced_nodes_offset, 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
    calculate_sliced_nodes_parameters
    if @sliced_nodes_null_relation
      # it's an empty set
      return sliced_nodes
    end
    relation_limit = @sliced_nodes_limit
    relation_offset = @sliced_nodes_offset
    if first && (relation_limit.nil? || relation_limit > first)
      # `first` would create a stricter limit that the one already applied, so add it
      relation_limit = first
    end
    if last
      if relation_limit
        if last <= relation_limit
          # `last` is a smaller slice than the current limit, so apply it
          relation_offset += (relation_limit - last)
          relation_limit = last
        end
      else
        # No limit, so get the last items
        sliced_nodes_count = relation_count(sliced_nodes)
        relation_offset += (sliced_nodes_count - [last, sliced_nodes_count].min)
        relation_limit = last
      end
    end
    @paged_nodes_offset = relation_offset
    paginated_nodes = items
    paginated_nodes = set_offset(paginated_nodes, relation_offset)
    if relation_limit
      paginated_nodes = set_limit(paginated_nodes, relation_limit)
    end
    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, _initial_offset, 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
  • _initial_offset (Integer) -- The number of items already excluded from the relation
  • relation (Object) -- A database query object
def relation_larger_than(relation, _initial_offset, 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
    calculate_sliced_nodes_parameters
    paginated_nodes = items
    if @sliced_nodes_null_relation
      paginated_nodes = null_relation(paginated_nodes)
    else
      if @sliced_nodes_limit
        paginated_nodes = set_limit(paginated_nodes, @sliced_nodes_limit)
      end
      if @sliced_nodes_offset
        paginated_nodes = set_offset(paginated_nodes, @sliced_nodes_offset)
      end
    end
    paginated_nodes
  end
end