Revision 0fc82c30
Von Moritz Bunkus vor fast 12 Jahren hinzugefügt
SL/DB/Helper/ActsAsList.pm | ||
---|---|---|
3 | 3 |
use strict; |
4 | 4 |
|
5 | 5 |
use parent qw(Exporter); |
6 |
our @EXPORT = qw(move_position_up move_position_down reorder_list); |
|
6 |
our @EXPORT = qw(move_position_up move_position_down reorder_list configure_acts_as_list);
|
|
7 | 7 |
|
8 | 8 |
use Carp; |
9 | 9 |
|
10 |
my %list_spec; |
|
11 |
|
|
10 | 12 |
sub import { |
11 | 13 |
my ($class, @params) = @_; |
12 | 14 |
my $importing = caller(); |
... | ... | |
54 | 56 |
return $result; |
55 | 57 |
} |
56 | 58 |
|
59 |
sub configure_acts_as_list { |
|
60 |
my ($class, %params) = @_; |
|
61 |
|
|
62 |
$list_spec{$class} = { |
|
63 |
group_by => $params{group_by}, |
|
64 |
column_name => $params{column_name}, |
|
65 |
}; |
|
66 |
} |
|
67 |
|
|
57 | 68 |
# |
58 | 69 |
# Helper functions |
59 | 70 |
# |
60 | 71 |
|
72 |
sub get_group_by_where { |
|
73 |
my ($self) = @_; |
|
74 |
|
|
75 |
my $group_by = get_spec(ref $self, 'group_by') || []; |
|
76 |
$group_by = [ $group_by ] if $group_by && !ref $group_by; |
|
77 |
|
|
78 |
my @where = map { my $value = $self->$_; defined($value) ? "(${_} = " . $value . ")" : "(${_} IS NULL)" } @{ $group_by }; |
|
79 |
|
|
80 |
return join ' AND ', @where; |
|
81 |
} |
|
82 |
|
|
61 | 83 |
sub set_position { |
62 | 84 |
my ($self) = @_; |
63 | 85 |
my $column = column_name($self); |
64 | 86 |
|
65 |
if (!defined $self->$column) { |
|
66 |
my $max_position = $self->db->dbh->selectrow_arrayref(qq|SELECT COALESCE(max(${column}), 0) FROM | . $self->meta->table)->[0]; |
|
67 |
$self->$column($max_position + 1); |
|
68 |
} |
|
87 |
return 1 if defined $self->$column; |
|
88 |
|
|
89 |
my $table = $self->meta->table; |
|
90 |
my $where = get_group_by_where($self); |
|
91 |
$where = " WHERE ${where}" if $where; |
|
92 |
my $sql = <<SQL; |
|
93 |
SELECT COALESCE(max(${column}), 0) |
|
94 |
FROM ${table} |
|
95 |
${where} |
|
96 |
SQL |
|
97 |
|
|
98 |
my $max_position = $self->db->dbh->selectrow_arrayref($sql)->[0]; |
|
99 |
$self->$column($max_position + 1); |
|
69 | 100 |
|
70 | 101 |
return 1; |
71 | 102 |
} |
... | ... | |
75 | 106 |
my $column = column_name($self); |
76 | 107 |
|
77 | 108 |
$self->load; |
78 |
if (defined $self->$column) { |
|
79 |
$self->_get_manager_class->update_all(set => { $column => \"${column} - 1" }, |
|
80 |
where => [ $column => { gt => $self->$column } ]); |
|
81 |
} |
|
109 |
return 1 unless defined $self->$column; |
|
110 |
|
|
111 |
my $table = $self->meta->table; |
|
112 |
my $value = $self->$column; |
|
113 |
my $group_by = get_group_by_where($self); |
|
114 |
$group_by = ' AND ' . $group_by if $group_by; |
|
115 |
my $sql = <<SQL; |
|
116 |
UPDATE ${table} |
|
117 |
SET ${column} = ${column} - 1 |
|
118 |
WHERE (${column} > ${value}) ${group_by} |
|
119 |
SQL |
|
120 |
|
|
121 |
$self->db->dbh->do($sql); |
|
82 | 122 |
|
83 | 123 |
return 1; |
84 | 124 |
} |
... | ... | |
90 | 130 |
croak "Object has not been saved yet" unless $self->id; |
91 | 131 |
croak "No position set yet" unless defined $self->$column; |
92 | 132 |
|
93 |
my ($comp_sql, $comp_rdbo, $min_max, $plus_minus) = $direction eq 'up' ? ('<', 'ge', 'max', '+') : ('>', 'le', 'min', '-'); |
|
133 |
my $table = $self->meta->table; |
|
134 |
my $old_position = $self->$column; |
|
135 |
my ($comp_sel, $comp_upd, $min_max, $plus_minus) = $direction eq 'up' ? ('<', '>=', 'max', '+') : ('>', '<=', 'min', '-'); |
|
136 |
my $group_by = get_group_by_where($self); |
|
137 |
$group_by = ' AND ' . $group_by if $group_by; |
|
138 |
my $sql = <<SQL; |
|
139 |
SELECT ${min_max}(${column}) |
|
140 |
FROM ${table} |
|
141 |
WHERE (${column} ${comp_sel} ${old_position}) |
|
142 |
${group_by} |
|
143 |
SQL |
|
94 | 144 |
|
95 |
my $new_position = $self->db->dbh->selectrow_arrayref(qq|SELECT ${min_max}(${column}) FROM | . $self->meta->table . qq| WHERE ${column} ${comp_sql} | . $self->$column)->[0];
|
|
145 |
my $new_position = $self->db->dbh->selectrow_arrayref($sql)->[0];
|
|
96 | 146 |
|
97 | 147 |
return undef unless defined $new_position; |
98 | 148 |
|
99 |
$self->_get_manager_class->update_all(set => { $column => $self->$column }, |
|
100 |
where => [ $column => $new_position ]); |
|
149 |
$sql = <<SQL; |
|
150 |
UPDATE ${table} |
|
151 |
SET ${column} = ${old_position} |
|
152 |
WHERE (${column} = ${new_position}) |
|
153 |
${group_by}; |
|
154 |
SQL |
|
155 |
|
|
156 |
$self->db->dbh->do($sql); |
|
157 |
|
|
101 | 158 |
$self->update_attributes($column => $new_position); |
102 | 159 |
} |
103 | 160 |
|
104 | 161 |
sub column_name { |
105 | 162 |
my ($self) = @_; |
163 |
my $column = get_spec(ref $self, 'column_name'); |
|
164 |
return $column if $column; |
|
106 | 165 |
return $self->can('sortkey') ? 'sortkey' : 'position'; |
107 | 166 |
} |
108 | 167 |
|
168 |
sub get_spec { |
|
169 |
my ($class, $key) = @_; |
|
170 |
|
|
171 |
return undef unless $list_spec{$class}; |
|
172 |
return $list_spec{$class}->{$key}; |
|
173 |
} |
|
174 |
|
|
109 | 175 |
1; |
110 | 176 |
__END__ |
111 | 177 |
|
... | ... | |
116 | 182 |
=head1 NAME |
117 | 183 |
|
118 | 184 |
SL::DB::Helper::ActsAsList - Mixin for managing ordered items by a |
119 |
column I<position> or I<sortkey>
|
|
185 |
column |
|
120 | 186 |
|
121 | 187 |
=head1 SYNOPSIS |
122 | 188 |
|
... | ... | |
146 | 212 |
When the object is deleted all positions greater than the object's old |
147 | 213 |
position are decreased by one. |
148 | 214 |
|
149 |
=head1 FUNCTIONS |
|
215 |
The column name to use can be configured via L<configure_acts_as_list>. |
|
216 |
|
|
217 |
=head1 CLASS FUNCTIONS |
|
218 |
|
|
219 |
=over 4 |
|
220 |
|
|
221 |
=item C<configure_acts_as_list %params> |
|
222 |
|
|
223 |
Configures the mixin's behaviour. C<%params> can contain the following |
|
224 |
values: |
|
225 |
|
|
226 |
=over 2 |
|
227 |
|
|
228 |
=item C<column_name> |
|
229 |
|
|
230 |
The name of the column containing the position. If not set explicitly |
|
231 |
then the mixin will use C<sortkey> if the model contains such a column |
|
232 |
(only for legacy tables) and C<position> otherwise. |
|
233 |
|
|
234 |
=item C<group_by> |
|
235 |
|
|
236 |
An optional column name (or array reference of column names) by which |
|
237 |
to group. If a table contains items for several distinct sets and each |
|
238 |
set has its own sorting then this can be used. |
|
239 |
|
|
240 |
An example would be requirement spec text blocks. They have a column |
|
241 |
called C<output_position> that selects where to output the text blocks |
|
242 |
(either before or after the sections). Furthermore these text blocks |
|
243 |
each belong to a single requirement spec document. So each combination |
|
244 |
of C<requirement_spec_id> and C<output_position> should have its own |
|
245 |
set of C<position> values, which can be achieved by configuring this |
|
246 |
mixin with C<group_by = [qw(requirement_spec_id output_position)]>. |
|
247 |
|
|
248 |
=back |
|
249 |
|
|
250 |
=back |
|
251 |
|
|
252 |
=head1 INSTANCE FUNCTIONS |
|
150 | 253 |
|
151 | 254 |
=over 4 |
152 | 255 |
|
Auch abrufbar als: Unified diff
ActsAsList: Möglichkeit, Positionsgruppen nach anderen Spalten anzugeben