File Coverage

blib/lib/DBIx/Class/Ordered.pm
Criterion Covered Total %
statement 198 205 96.5
branch 71 82 86.5
condition 9 14 64.2
subroutine 37 38 97.3
pod 16 16 100.0
total 331 355 93.2


line stmt bran cond sub pod time code
1             package DBIx::Class::Ordered;
2 329     329   195017 use strict;
  329         1801  
  329         47555  
3 329     329   12323 use warnings;
  329         2786  
  329         11136  
4 329     329   2185 use base qw( DBIx::Class );
  329         609  
  329         22054  
5              
6 329     329   2306 use List::Util 'first';
  329         643  
  329         22706  
7 329     329   3143 use namespace::clean;
  329         622  
  329         4121  
8              
9             =head1 NAME
10              
11             DBIx::Class::Ordered - Modify the position of objects in an ordered list.
12              
13             =head1 SYNOPSIS
14              
15             Create a table for your ordered data.
16              
17             CREATE TABLE items (
18             item_id INTEGER PRIMARY KEY AUTOINCREMENT,
19             name TEXT NOT NULL,
20             position INTEGER NOT NULL
21             );
22              
23             Optionally, add one or more columns to specify groupings, allowing you
24             to maintain independent ordered lists within one table:
25              
26             CREATE TABLE items (
27             item_id INTEGER PRIMARY KEY AUTOINCREMENT,
28             name TEXT NOT NULL,
29             position INTEGER NOT NULL,
30             group_id INTEGER NOT NULL
31             );
32              
33             Or even
34              
35             CREATE TABLE items (
36             item_id INTEGER PRIMARY KEY AUTOINCREMENT,
37             name TEXT NOT NULL,
38             position INTEGER NOT NULL,
39             group_id INTEGER NOT NULL,
40             other_group_id INTEGER NOT NULL
41             );
42              
43             In your Schema or DB class add "Ordered" to the top
44             of the component list.
45              
46             __PACKAGE__->load_components(qw( Ordered ... ));
47              
48             Specify the column that stores the position number for
49             each row.
50              
51             package My::Item;
52             __PACKAGE__->position_column('position');
53              
54             If you are using one grouping column, specify it as follows:
55              
56             __PACKAGE__->grouping_column('group_id');
57              
58             Or if you have multiple grouping columns:
59              
60             __PACKAGE__->grouping_column(['group_id', 'other_group_id']);
61              
62             That's it, now you can change the position of your objects.
63              
64             #!/use/bin/perl
65             use My::Item;
66              
67             my $item = My::Item->create({ name=>'Matt S. Trout' });
68             # If using grouping_column:
69             my $item = My::Item->create({ name=>'Matt S. Trout', group_id=>1 });
70              
71             my $rs = $item->siblings();
72             my @siblings = $item->siblings();
73              
74             my $sibling;
75             $sibling = $item->first_sibling();
76             $sibling = $item->last_sibling();
77             $sibling = $item->previous_sibling();
78             $sibling = $item->next_sibling();
79              
80             $item->move_previous();
81             $item->move_next();
82             $item->move_first();
83             $item->move_last();
84             $item->move_to( $position );
85             $item->move_to_group( 'groupname' );
86             $item->move_to_group( 'groupname', $position );
87             $item->move_to_group( {group_id=>'groupname', 'other_group_id=>'othergroupname'} );
88             $item->move_to_group( {group_id=>'groupname', 'other_group_id=>'othergroupname'}, $position );
89              
90             =head1 DESCRIPTION
91              
92             This module provides a simple interface for modifying the ordered
93             position of DBIx::Class objects.
94              
95             =head1 AUTO UPDATE
96              
97             All of the move_* methods automatically update the rows involved in
98             the query. This is not configurable and is due to the fact that if you
99             move a record it always causes other records in the list to be updated.
100              
101             =head1 METHODS
102              
103             =head2 position_column
104              
105             __PACKAGE__->position_column('position');
106              
107             Sets and retrieves the name of the column that stores the
108             positional value of each record. Defaults to "position".
109              
110             =cut
111              
112             __PACKAGE__->mk_classdata( 'position_column' => 'position' );
113              
114             =head2 grouping_column
115              
116             __PACKAGE__->grouping_column('group_id');
117              
118             This method specifies a column to limit all queries in
119             this module by. This effectively allows you to have multiple
120             ordered lists within the same table.
121              
122             =cut
123              
124             __PACKAGE__->mk_classdata( 'grouping_column' );
125              
126             =head2 null_position_value
127              
128             __PACKAGE__->null_position_value(undef);
129              
130             This method specifies a value of L which B
131             never be assigned to a row> during normal operation. When
132             a row is moved, its position is set to this value temporarily, so
133             that any unique constraints can not be violated. This value defaults
134             to 0, which should work for all cases except when your positions do
135             indeed start from 0.
136              
137             =cut
138              
139             __PACKAGE__->mk_classdata( 'null_position_value' => 0 );
140              
141             =head2 siblings
142              
143             my $rs = $item->siblings();
144             my @siblings = $item->siblings();
145              
146             Returns an B resultset of all other objects in the same
147             group excluding the one you called it on.
148              
149             The ordering is a backwards-compatibility artifact - if you need
150             a resultset with no ordering applied use C<_siblings>
151              
152             =cut
153             sub siblings {
154 0     0 1 0 my $self = shift;
155 0         0 return $self->_siblings->search ({}, { order_by => $self->position_column } );
156             }
157              
158             =head2 previous_siblings
159              
160             my $prev_rs = $item->previous_siblings();
161             my @prev_siblings = $item->previous_siblings();
162              
163             Returns a resultset of all objects in the same group
164             positioned before the object on which this method was called.
165              
166             =cut
167             sub previous_siblings {
168 390     390 1 495 my $self = shift;
169 390         8239 my $position_column = $self->position_column;
170 390         12510 my $position = $self->get_column ($position_column);
171 390 50       1579 return ( defined $position
172             ? $self->_siblings->search ({ $position_column => { '<', $position } })
173             : $self->_siblings
174             );
175             }
176              
177             =head2 next_siblings
178              
179             my $next_rs = $item->next_siblings();
180             my @next_siblings = $item->next_siblings();
181              
182             Returns a resultset of all objects in the same group
183             positioned after the object on which this method was called.
184              
185             =cut
186             sub next_siblings {
187 841     841 1 1219 my $self = shift;
188 841         18384 my $position_column = $self->position_column;
189 841         27466 my $position = $self->get_column ($position_column);
190 841 100       3862 return ( defined $position
191             ? $self->_siblings->search ({ $position_column => { '>', $position } })
192             : $self->_siblings
193             );
194             }
195              
196             =head2 previous_sibling
197              
198             my $sibling = $item->previous_sibling();
199              
200             Returns the sibling that resides one position back. Returns 0
201             if the current object is the first one.
202              
203             =cut
204              
205             sub previous_sibling {
206 260     260 1 796 my $self = shift;
207 260         7112 my $position_column = $self->position_column;
208              
209 260         12059 my $psib = $self->previous_siblings->search(
210             {},
211             { rows => 1, order_by => { '-desc' => $position_column } },
212             )->single;
213              
214 260 100       1005 return defined $psib ? $psib : 0;
215             }
216              
217             =head2 first_sibling
218              
219             my $sibling = $item->first_sibling();
220              
221             Returns the first sibling object, or 0 if the first sibling
222             is this sibling.
223              
224             =cut
225              
226             sub first_sibling {
227 130     130 1 182 my $self = shift;
228 130         3708 my $position_column = $self->position_column;
229              
230 130         6211 my $fsib = $self->previous_siblings->search(
231             {},
232             { rows => 1, order_by => { '-asc' => $position_column } },
233             )->single;
234              
235 130 100       502 return defined $fsib ? $fsib : 0;
236             }
237              
238             =head2 next_sibling
239              
240             my $sibling = $item->next_sibling();
241              
242             Returns the sibling that resides one position forward. Returns 0
243             if the current object is the last one.
244              
245             =cut
246              
247             sub next_sibling {
248 260     260 1 652 my $self = shift;
249 260         7497 my $position_column = $self->position_column;
250 260         12746 my $nsib = $self->next_siblings->search(
251             {},
252             { rows => 1, order_by => { '-asc' => $position_column } },
253             )->single;
254              
255 260 100       1129 return defined $nsib ? $nsib : 0;
256             }
257              
258             =head2 last_sibling
259              
260             my $sibling = $item->last_sibling();
261              
262             Returns the last sibling, or 0 if the last sibling is this
263             sibling.
264              
265             =cut
266              
267             sub last_sibling {
268 130     130 1 204 my $self = shift;
269 130         3692 my $position_column = $self->position_column;
270 130         6588 my $lsib = $self->next_siblings->search(
271             {},
272             { rows => 1, order_by => { '-desc' => $position_column } },
273             )->single;
274              
275 130 100       586 return defined $lsib ? $lsib : 0;
276             }
277              
278             # an optimized method to get the last sibling position value without inflating a result object
279             sub _last_sibling_posval {
280 451     451   609 my $self = shift;
281 451         10643 my $position_column = $self->position_column;
282              
283 451         17776 my $cursor = $self->next_siblings->search(
284             {},
285             { rows => 1, order_by => { '-desc' => $position_column }, select => $position_column },
286             )->cursor;
287              
288 451         1416 my ($pos) = $cursor->next;
289 451         1600 return $pos;
290             }
291              
292             =head2 move_previous
293              
294             $item->move_previous();
295              
296             Swaps position with the sibling in the position previous in
297             the list. Returns 1 on success, and 0 if the object is
298             already the first one.
299              
300             =cut
301              
302             sub move_previous {
303 65     65 1 101 my $self = shift;
304 65         492 return $self->move_to ($self->_position - 1);
305             }
306              
307             =head2 move_next
308              
309             $item->move_next();
310              
311             Swaps position with the sibling in the next position in the
312             list. Returns 1 on success, and 0 if the object is already
313             the last in the list.
314              
315             =cut
316              
317             sub move_next {
318 65     65 1 107 my $self = shift;
319 65 100       244 return 0 unless defined $self->_last_sibling_posval; # quick way to check for no more siblings
320 51         1182 return $self->move_to ($self->_position + 1);
321             }
322              
323             =head2 move_first
324              
325             $item->move_first();
326              
327             Moves the object to the first position in the list. Returns 1
328             on success, and 0 if the object is already the first.
329              
330             =cut
331              
332             sub move_first {
333 65     65 1 278 return shift->move_to( 1 );
334             }
335              
336             =head2 move_last
337              
338             $item->move_last();
339              
340             Moves the object to the last position in the list. Returns 1
341             on success, and 0 if the object is already the last one.
342              
343             =cut
344              
345             sub move_last {
346 129     129 1 201 my $self = shift;
347 129         478 my $last_posval = $self->_last_sibling_posval;
348              
349 129 100       2999 return 0 unless defined $last_posval;
350              
351 82         410 return $self->move_to( $self->_position_from_value ($last_posval) );
352             }
353              
354             =head2 move_to
355              
356             $item->move_to( $position );
357              
358             Moves the object to the specified position. Returns 1 on
359             success, and 0 if the object is already at the specified
360             position.
361              
362             =cut
363              
364             sub move_to {
365 578     578 1 940 my( $self, $to_position ) = @_;
366 578 100       1630 return 0 if ( $to_position < 1 );
367              
368 564         14710 my $position_column = $self->position_column;
369              
370 564         23038 my $is_txn;
371 564 100       2521 if ($is_txn = $self->result_source->schema->storage->transaction_depth) {
    100          
372             # Reload position state from storage
373             # The thinking here is that if we are in a transaction, it is
374             # *more likely* the object went out of sync due to resultset
375             # level shenanigans. Instead of always reloading (slow) - go
376             # ahead and hand-hold only in the case of higher layers
377             # requesting the safety of a txn
378              
379 31   33     446 $self->store_column(
380             $position_column,
381             ( $self->result_source
382             ->resultset
383             ->search($self->_storage_ident_condition, { rows => 1, columns => $position_column })
384             ->cursor
385             ->next
386             )[0] || $self->throw_exception(
387             sprintf "Unable to locate object '%s' in storage - object went ouf of sync...?",
388             $self->ID
389             ),
390             );
391 31         118 delete $self->{_dirty_columns}{$position_column};
392             }
393             elsif ($self->is_column_changed ($position_column) ) {
394             # something changed our position, we need to know where we
395             # used to be - use the stashed value
396 2         7 $self->store_column($position_column, delete $self->{_column_data_in_storage}{$position_column});
397 2         3 delete $self->{_dirty_columns}{$position_column};
398             }
399              
400 564         1635 my $from_position = $self->_position;
401              
402 564 100       1760 if ( $from_position == $to_position ) { # FIXME this will not work for non-numeric order
403 80         207 return 0;
404             }
405              
406 484 100       1706 my $guard = $is_txn ? undef : $self->result_source->schema->txn_scope_guard;
407              
408 484         760 my ($direction, @between);
409 484 100       1257 if ( $from_position < $to_position ) {
410 258         435 $direction = -1;
411 258         533 @between = map { $self->_position_value ($_) } ( $from_position + 1, $to_position );
  516         1155  
412             }
413             else {
414 226         366 $direction = 1;
415 226         693 @between = map { $self->_position_value ($_) } ( $to_position, $from_position - 1 );
  452         1107  
416             }
417              
418 484         1008 my $new_pos_val = $self->_position_value ($to_position); # record this before the shift
419              
420             # we need to null-position the moved row if the position column is part of a constraint
421 484 100       807 if (grep { $_ eq $position_column } ( map { @$_ } (values %{{ $self->result_source->unique_constraints }} ) ) ) {
  552         2178  
  518         1316  
  484         1504  
422 17         449 $self->_ordered_internal_update({ $position_column => $self->null_position_value });
423             }
424              
425 484         2738 $self->_shift_siblings ($direction, @between);
426 484         3702 $self->_ordered_internal_update({ $position_column => $new_pos_val });
427              
428 484 100       2815 $guard->commit if $guard;
429 484         2376 return 1;
430             }
431              
432             =head2 move_to_group
433              
434             $item->move_to_group( $group, $position );
435              
436             Moves the object to the specified position of the specified
437             group, or to the end of the group if $position is undef.
438             1 is returned on success, and 0 is returned if the object is
439             already at the specified position of the specified group.
440              
441             $group may be specified as a single scalar if only one
442             grouping column is in use, or as a hashref of column => value pairs
443             if multiple grouping columns are in use.
444              
445             =cut
446              
447             sub move_to_group {
448 19     19 1 67 my( $self, $to_group, $to_position ) = @_;
449              
450             # if we're given a single value, turn it into a hashref
451 19 100       74 unless (ref $to_group eq 'HASH') {
452 6         20 my @gcols = $self->_grouping_columns;
453              
454 6 50       26 $self->throw_exception ('Single group supplied for a multi-column group identifier') if @gcols > 1;
455 6         19 $to_group = {$gcols[0] => $to_group};
456             }
457              
458 19         494 my $position_column = $self->position_column;
459              
460 19 50 66     800 return 0 if ( defined($to_position) and $to_position < 1 );
461              
462             # check if someone changed the _grouping_columns - this will
463             # prevent _is_in_group working, so we need to restore the
464             # original stashed values
465 19         55 for ($self->_grouping_columns) {
466 24 100       75 if ($self->is_column_changed ($_)) {
467 16         60 $self->store_column($_, delete $self->{_column_data_in_storage}{$_});
468 16         45 delete $self->{_dirty_columns}{$_};
469             }
470             }
471              
472 19 50       77 if ($self->_is_in_group ($to_group) ) {
473 0         0 my $ret;
474 0 0       0 if (defined $to_position) {
475 0         0 $ret = $self->move_to ($to_position);
476             }
477              
478 0   0     0 return $ret||0;
479             }
480              
481 19         62 my $guard = $self->result_source->schema->txn_scope_guard;
482              
483             # Move to end of current group to adjust siblings
484 19         88 $self->move_last;
485              
486 19         152 $self->set_inflated_columns({ %$to_group, $position_column => undef });
487 19         88 my $new_group_last_posval = $self->_last_sibling_posval;
488 19         374 my $new_group_last_position = $self->_position_from_value (
489             $new_group_last_posval
490             );
491              
492 19 100 100     126 if ( not defined($to_position) or $to_position > $new_group_last_position) {
493 5 50       35 $self->set_column(
494             $position_column => $new_group_last_position
495             ? $self->_next_position_value ( $new_group_last_posval )
496             : $self->_initial_position_value
497             );
498             }
499             else {
500 14         41 my $bumped_pos_val = $self->_position_value ($to_position);
501 14         34 my @between = map { $self->_position_value ($_) } ($to_position, $new_group_last_position);
  28         49  
502 14         52 $self->_shift_siblings (1, @between); #shift right
503 14         96 $self->set_column( $position_column => $bumped_pos_val );
504             }
505              
506 19         74 $self->_ordered_internal_update;
507              
508 19         133 $guard->commit;
509              
510 19         93 return 1;
511             }
512              
513             =head2 insert
514              
515             Overrides the DBIC insert() method by providing a default
516             position number. The default will be the number of rows in
517             the table +1, thus positioning the new record at the last position.
518              
519             =cut
520              
521             sub insert {
522 263     263 1 379 my $self = shift;
523 263         6912 my $position_column = $self->position_column;
524              
525 263 100       12729 unless ($self->get_column($position_column)) {
526 238         698 my $lsib_posval = $self->_last_sibling_posval;
527 238 100       7489 $self->set_column(
528             $position_column => (defined $lsib_posval
529             ? $self->_next_position_value ( $lsib_posval )
530             : $self->_initial_position_value
531             )
532             );
533             }
534              
535 263         1224 return $self->next::method( @_ );
536             }
537              
538             =head2 update
539              
540             Overrides the DBIC update() method by checking for a change
541             to the position and/or group columns. Movement within a
542             group or to another group is handled by repositioning
543             the appropriate siblings. Position defaults to the end
544             of a new group if it has been changed to undef.
545              
546             =cut
547              
548             sub update {
549 570     570 1 1041 my $self = shift;
550              
551             # this is set by _ordered_internal_update()
552 570 100       1574 return $self->next::method(@_) if $self->result_source->schema->{_ORDERED_INTERNAL_UPDATE};
553              
554 16         39 my $upd = shift;
555 16 100       84 $self->set_inflated_columns($upd) if $upd;
556              
557 16         323 my $position_column = $self->position_column;
558 16         516 my @group_columns = $self->_grouping_columns;
559              
560             # see if the order is already changed
561 16         43 my $changed_ordering_cols = { map { $_ => $self->get_column($_) } grep { $self->is_column_changed($_) } ($position_column, @group_columns) };
  23         57  
  37         138  
562              
563             # nothing changed - short circuit
564 16 100       124 if (! keys %$changed_ordering_cols) {
    100          
565 1         4 return $self->next::method( undef, @_ );
566             }
567 15     15   53 elsif (defined first { exists $changed_ordering_cols->{$_} } @group_columns ) {
568             $self->move_to_group(
569             # since the columns are already re-set the _grouping_clause is correct
570             # move_to_group() knows how to get the original storage values
571             { $self->_grouping_clause },
572              
573             # The FIXME bit contradicts the documentation: POD states that
574             # when changing groups without supplying explicit positions in
575             # move_to_group(), we push the item to the end of the group.
576             # However when I was rewriting this, the position from the old
577             # group was clearly passed to the new one
578             # Probably needs to go away (by ribasushi)
579             (exists $changed_ordering_cols->{$position_column}
580 13 100       46 ? $changed_ordering_cols->{$position_column} # means there was a position change supplied with the update too
581             : $self->_position # FIXME! (replace with undef)
582             ),
583             );
584             }
585             else {
586 2         7 $self->move_to($changed_ordering_cols->{$position_column});
587             }
588              
589 15         152 return $self;
590             }
591              
592             =head2 delete
593              
594             Overrides the DBIC delete() method by first moving the object
595             to the last position, then deleting it, thus ensuring the
596             integrity of the positions.
597              
598             =cut
599              
600             sub delete {
601 45     45 1 81 my $self = shift;
602              
603 45         224 my $guard = $self->result_source->schema->txn_scope_guard;
604              
605 45         222 $self->move_last;
606              
607 45         189 $self->next::method( @_ );
608              
609 45         162 $guard->commit;
610              
611 45         142 return $self;
612             }
613              
614             # add the current position/group to the things we track old values for
615             sub _track_storage_value {
616 600     600   988 my ($self, $col) = @_;
617 600   100 648   1944 return $self->next::method($col) || defined first { $_ eq $col } ($self->position_column, $self->_grouping_columns);
  648         5407  
618             }
619              
620             =head1 METHODS FOR EXTENDING ORDERED
621              
622             You would want to override the methods below if you use sparse
623             (non-linear) or non-numeric position values. This can be useful
624             if you are working with preexisting non-normalised position data,
625             or if you need to work with materialized path columns.
626              
627             =head2 _position_from_value
628              
629             my $num_pos = $item->_position_from_value ( $pos_value )
630              
631             Returns the B of an object with a B
632             value> set to C<$pos_value>. By default simply returns C<$pos_value>.
633              
634             =cut
635             sub _position_from_value {
636 789     789   1021 my ($self, $val) = @_;
637              
638 789 50       1640 return 0 unless defined $val;
639              
640             # #the right way to do this
641             # return $self -> _group_rs
642             # -> search({ $self->position_column => { '<=', $val } })
643             # -> count
644              
645 789         1791 return $val;
646             }
647              
648             =head2 _position_value
649              
650             my $pos_value = $item->_position_value ( $pos )
651              
652             Returns the B of L of the object at numeric
653             position C<$pos>. By default simply returns C<$pos>.
654              
655             =cut
656             sub _position_value {
657 1494     1494   1553 my ($self, $pos) = @_;
658              
659             # #the right way to do this (not optimized)
660             # my $position_column = $self->position_column;
661             # return $self -> _group_rs
662             # -> search({}, { order_by => $position_column })
663             # -> slice ( $pos - 1)
664             # -> single
665             # -> get_column ($position_column);
666              
667 1494         2961 return $pos;
668             }
669              
670             =head2 _initial_position_value
671              
672             __PACKAGE__->_initial_position_value(0);
673              
674             This method specifies a B of L which is assigned
675             to the first inserted element of a group, if no value was supplied at
676             insertion time. All subsequent values are derived from this one by
677             L below. Defaults to 1.
678              
679             =cut
680              
681             __PACKAGE__->mk_classdata( '_initial_position_value' => 1 );
682              
683             =head2 _next_position_value
684              
685             my $new_value = $item->_next_position_value ( $position_value )
686              
687             Returns a position B that would be considered C with
688             regards to C<$position_value>. Can be pretty much anything, given
689             that C<< $position_value < $new_value >> where C<< < >> is the
690             SQL comparison operator (usually works fine on strings). The
691             default method expects C<$position_value> to be numeric, and
692             returns C<$position_value + 1>
693              
694             =cut
695             sub _next_position_value {
696 174     174   740 return $_[1] + 1;
697             }
698              
699             =head2 _shift_siblings
700              
701             $item->_shift_siblings ($direction, @between)
702              
703             Shifts all siblings with B in the range @between
704             (inclusive) by one position as specified by $direction (left if < 0,
705             right if > 0). By default simply increments/decrements each
706             L value by 1, doing so in a way as to not violate
707             any existing constraints.
708              
709             Note that if you override this method and have unique constraints
710             including the L the shift is not a trivial task.
711             Refer to the implementation source of the default method for more
712             information.
713              
714             =cut
715             sub _shift_siblings {
716 498     498   1111 my ($self, $direction, @between) = @_;
717 498 50       1252 return 0 unless $direction;
718              
719 498         12907 my $position_column = $self->position_column;
720              
721 498         18179 my ($op, $ord);
722 498 100       1466 if ($direction < 0) {
723 258         405 $op = '-';
724 258         359 $ord = 'asc';
725             }
726             else {
727 240         430 $op = '+';
728 240         483 $ord = 'desc';
729             }
730              
731 498         1363 my $shift_rs = $self->_group_rs-> search ({ $position_column => { -between => \@between } });
732              
733             # some databases (sqlite, pg, perhaps others) are dumb and can not do a
734             # blanket increment/decrement without violating a unique constraint.
735             # So what we do here is check if the position column is part of a unique
736             # constraint, and do a one-by-one update if this is the case.
737 498         1958 my $rsrc = $self->result_source;
738              
739             # set in case there are more cascades combined with $rs->update => $rs_update_all overrides
740 498         1913 local $rsrc->schema->{_ORDERED_INTERNAL_UPDATE} = 1;
741 498         1905 my @pcols = $rsrc->primary_columns;
742 498 100       2334 if (
743 551     551   1316 first { $_ eq $position_column } ( map { @$_ } (values %{{ $rsrc->unique_constraints }} ) )
  532         2256  
  498         1469  
744             ) {
745 17         51 my $clean_rs = $rsrc->resultset;
746              
747 17         193 for ( $shift_rs->search (
748             {}, { order_by => { "-$ord", $position_column }, select => [$position_column, @pcols] }
749             )->cursor->all ) {
750 34         76 my $pos = shift @$_;
751 34 50       174 $clean_rs->find(@$_)->update ({ $position_column => $pos + ( ($op eq '+') ? 1 : -1 ) });
752             }
753             }
754             else {
755 481         3092 $shift_rs->update ({ $position_column => \ "$position_column $op 1" } );
756             }
757             }
758              
759              
760             # This method returns a resultset containing all members of the row
761             # group (including the row itself).
762             sub _group_rs {
763 1729     1729   2381 my $self = shift;
764 1729         4869 return $self->result_source->resultset->search({$self->_grouping_clause()});
765             }
766              
767             # Returns an unordered resultset of all objects in the same group
768             # excluding the object you called this method on.
769             sub _siblings {
770 1231     1231   1649 my $self = shift;
771 1231         25483 my $position_column = $self->position_column;
772 1231         36817 my $pos;
773 1231 100       3122 return defined ($pos = $self->get_column($position_column))
774             ? $self->_group_rs->search(
775             { $position_column => { '!=' => $pos } },
776             )
777             : $self->_group_rs
778             ;
779             }
780              
781             # Returns the B of the current object, with the
782             # first object being at position 1, its sibling at position 2 and so on.
783             sub _position {
784 688     688   1105 my $self = shift;
785 688         14530 return $self->_position_from_value ($self->get_column ($self->position_column) );
786             }
787              
788             # This method returns one or more name=>value pairs for limiting a search
789             # by the grouping column(s). If the grouping column is not defined then
790             # this will return an empty list.
791             sub _grouping_clause {
792 1761     1761   2272 my( $self ) = @_;
793 1761         4027 return map { $_ => $self->get_column($_) } $self->_grouping_columns();
  2523         7168  
794             }
795              
796             # Returns a list of the column names used for grouping, regardless of whether
797             # they were specified as an arrayref or a single string, and returns ()
798             # if there is no grouping.
799             sub _grouping_columns {
800 2401     2401   26305 my( $self ) = @_;
801 2401         52786 my $col = $self->grouping_column();
802 2401 100       65908 if (ref $col eq 'ARRAY') {
    100          
803 1130         3440 return @$col;
804             } elsif ($col) {
805 1119         3054 return ( $col );
806             } else {
807 152         747 return ();
808             }
809             }
810              
811             # Returns true if the object is in the group represented by hashref $other
812             sub _is_in_group {
813 19     19   31 my ($self, $other) = @_;
814 19         54 my $current = {$self->_grouping_clause};
815              
816 329     329   818667 no warnings qw/uninitialized/;
  329         826  
  329         57991  
817              
818 19 50       166 return 0 if (
819             join ("\x00", sort keys %$current)
820             ne
821             join ("\x00", sort keys %$other)
822             );
823 19         53 for my $key (keys %$current) {
824 21 100       141 return 0 if $current->{$key} ne $other->{$key};
825             }
826 0         0 return 1;
827             }
828              
829             # This is a short-circuited method, that is used internally by this
830             # module to update positioning values in isolation (i.e. without
831             # triggering any of the positioning integrity code).
832             #
833             # Some day you might get confronted by datasets that have ambiguous
834             # positioning data (e.g. duplicate position values within the same group,
835             # in a table without unique constraints). When manually fixing such data
836             # keep in mind that you can not invoke L like
837             # you normally would, as it will get confused by the wrong data before
838             # having a chance to update the ill-defined row. If you really know what
839             # you are doing use this method which bypasses any hooks introduced by
840             # this module.
841             sub _ordered_internal_update {
842 520     520   1741 my $self = shift;
843 520         1791 local $self->result_source->schema->{_ORDERED_INTERNAL_UPDATE} = 1;
844 520         1831 return $self->update (@_);
845             }
846              
847             1;
848              
849             __END__