NAME
DBIO::Ordered - Maintain a position column over an ordered list of rows
VERSION
version 0.900000
SYNOPSIS
package MyApp::Schema::Result::Item;
use base 'DBIO::Core';
__PACKAGE__->load_components(qw/Ordered/);
__PACKAGE__->table('items');
__PACKAGE__->add_columns(
item_id => { data_type => 'integer', is_auto_increment => 1 },
name => { data_type => 'varchar', size => 100 },
position => { data_type => 'integer', position => 1 },
);
__PACKAGE__->set_primary_key('item_id');
With one or more grouping columns for independent ordered lists per group:
__PACKAGE__->add_columns(
item_id => { data_type => 'integer', is_auto_increment => 1 },
name => { data_type => 'varchar', size => 100 },
position => { data_type => 'integer', position => 1 },
group_id => { data_type => 'integer', grouping => 1 },
);
In code:
my $rs = $item->siblings;
my $sibling = $item->first_sibling;
$item->move_previous;
$item->move_next;
$item->move_first;
$item->move_last;
$item->move_to($position);
$item->move_to_group('groupname');
$item->move_to_group('groupname', $position);
$item->move_to_group({ group_id => 'a', other_group_id => 'b' }, $position);
DESCRIPTION
Maintains a position column over an ordered list of rows. Mark the position column with position => 1 in add_columns; mark zero or more grouping columns with grouping => 1 to maintain independent ordered lists within the same table.
The move_* methods automatically update sibling rows to keep the order contiguous. This is not configurable -- moving one row in an ordered list always shifts others.
ATTRIBUTES
_initial_position_value
__PACKAGE__->_initial_position_value(0);
Position value assigned to the first row of a group when no value is supplied. Defaults to 1.
METHODS
position_column
my $col = $self->position_column;
Returns the name of the column flagged with position => 1. Throws if no such column is defined on the result source.
grouping_column
my $col_or_aref = $self->grouping_column;
Returns the column (or arrayref of columns) flagged with grouping => 1. Returns a single scalar when exactly one column is flagged, an arrayref when multiple are flagged, and undef when no grouping is configured.
null_position_value
my $val = $self->null_position_value;
Returns the value used as a placeholder while a row is being moved, so unique constraints involving the position column are not violated. Reads from the null_position_value flag on the position column; defaults to 0.
siblings
my $rs = $item->siblings;
Returns an ordered resultset of all other rows in the same group, excluding the row this was called on.
previous_siblings
my $rs = $item->previous_siblings;
Returns a resultset of all rows in the same group positioned before this one.
next_siblings
my $rs = $item->next_siblings;
Returns a resultset of all rows in the same group positioned after this one.
previous_sibling
Returns the row one position before this one, or 0 if this is the first.
first_sibling
Returns the first row in the group, or 0 if this is the first.
next_sibling
Returns the row one position after this one, or 0 if this is the last.
last_sibling
Returns the last row in the group, or 0 if this is the last.
move_previous
Swaps with the sibling one position before this one. Returns 1 on success, 0 if already first.
move_next
Swaps with the sibling one position after this one. Returns 1 on success, 0 if already last.
move_first
Moves to the first position. Returns 1 on success, 0 if already first.
move_last
Moves to the last position. Returns 1 on success, 0 if already last.
move_to
$item->move_to($position);
Moves to the specified position. Returns 1 on success, 0 if already at that position.
move_to_group
$item->move_to_group($group, $position);
Moves to the specified position of the specified group, or to the end of the group if $position is undef. Returns 1 on success, 0 if already at that position. $group may be a single scalar (when only one grouping column is in use) or a hashref of column => value pairs.
insert
Assigns a default position (one past the current last sibling) when the position column has no value set.
update
If the position or grouping columns changed, performs the move via "move_to" or "move_to_group" to keep siblings consistent.
delete
Moves to the last position before deleting, keeping the order tree contiguous.
_position_from_value
my $num_pos = $item->_position_from_value($pos_value);
Returns the absolute numeric position of a row given a raw position value. Default: returns $pos_value unchanged.
_position_value
my $pos_value = $item->_position_value($pos);
Returns the value of "position_column" for the row at numeric position $pos. Default: returns $pos unchanged.
_next_position_value
my $new_value = $item->_next_position_value($position_value);
Returns the next position value after $position_value. Default: $position_value + 1.
_shift_siblings
$item->_shift_siblings($direction, @between);
Shifts all siblings whose position values fall within the inclusive range @between by one position in the given direction (left if < 0, right if > 0). Handles unique-constraint cases by falling back to a one-by-one update.
COLUMN FLAGS
position => 1-
Marks the column that stores the integer position of each row.
grouping => 1-
Marks a column as a grouping key. Multiple columns may be flagged -- in that case all of them must match for two rows to be considered siblings.
null_position_value => $value-
Set on the position column. Specifies the placeholder value used while a row is mid-move, so unique constraints involving the position column are not violated. Defaults to
0; set toundefif your positions start at 0.
OVERRIDABLE METHODS
Override these in your Result class if you use sparse (non-linear) or non-numeric position values, e.g. when working with materialized path columns.
_position_from_value($value)-
Maps a stored position value to an absolute numeric position. Default: identity.
_position_value($position)-
Inverse of the above. Default: identity.
_next_position_value($value)-
Returns the next position value after
$value. Default:$value + 1. _initial_position_value-
Class data with the position value used for the first row of a group. Defaults to
1.
CAVEATS
Resultset methods
All insert/create/delete overrides happen on DBIO::Row. If you use the DBIO::ResultSet versions of update or delete, all logic in this component is bypassed. Use update_all / delete_all instead -- they invoke the row method on every member.
Race condition on insert
If no position is supplied at insert time, one is chosen based on "_initial_position_value" or "_next_position_value". The window between select and insert introduces a race. Add unique constraints on the position/group columns and use transactions to prevent silent corruption.
Multiple moves
When multiple same-group rows are loaded from storage, move_* operations on them can drift out of sync with the underlying storage. Wrapping in a transaction triggers an implicit reload; otherwise call "discard_changes" in DBIO::Row to refresh.
Default values
Database-side default values on grouping columns can result in incorrect position assignment.
AUTHOR
DBIO & DBIx::Class Authors
COPYRIGHT AND LICENSE
Copyright (C) 2026 DBIO Authors Portions Copyright (C) 2005-2025 DBIx::Class Authors Based on DBIx::Class, heavily modified.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.