class GraphQL::Pagination::RelationConnection
A generic class for working with database query objects.
def after_offset
-
(Integer, nil)
-
def after_offset @after_offset ||= after && offset_from_cursor(after) end
def before_offset
-
(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
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
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)
-
(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)
-
(Integer)
-
def offset_from_cursor(cursor) decode(cursor).to_i end
def relation_count(relation)
-
(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)
-
(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)
-
(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)
-
(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)
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)
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
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